Merge "Add metric for battery page in debug mode."
diff --git a/Android.mk b/Android.mk
index f7b6f46..c0c025b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -219,6 +219,8 @@
 	core/java/android/hardware/location/IGeofenceHardwareCallback.aidl \
 	core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
 	core/java/android/hardware/location/IContextHubCallback.aidl \
+	core/java/android/hardware/location/IContextHubClient.aidl \
+	core/java/android/hardware/location/IContextHubClientCallback.aidl \
 	core/java/android/hardware/location/IContextHubService.aidl \
 	core/java/android/hardware/location/IContextHubTransactionCallback.aidl \
 	core/java/android/hardware/radio/IRadioService.aidl \
@@ -559,6 +561,7 @@
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
 	wifi/java/android/net/wifi/rtt/IRttCallback.aidl \
 	wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \
+	wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl \
 	wifi/java/android/net/wifi/IWifiScanner.aidl \
 	wifi/java/android/net/wifi/IRttManager.aidl \
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
@@ -914,7 +917,7 @@
 # Search through the base framework dirs for these packages.
 # The result will be relative to frameworks/base.
 fwbase_dirs_to_document := \
-	legacy-test/src \
+	test-base/src \
 	$(patsubst $(LOCAL_PATH)/%,%, \
 	  $(wildcard \
 	    $(foreach dir, $(FRAMEWORKS_BASE_JAVA_SRC_DIRS), \
@@ -1030,6 +1033,7 @@
     -manifest ./frameworks/base/core/res/AndroidManifest.xml \
     -hidePackage com.android.okhttp \
     -hidePackage com.android.org.conscrypt \
+    -hidePackage com.android.server \
     -since $(SRC_API_DIR)/1.xml 1 \
     -since $(SRC_API_DIR)/2.xml 2 \
     -since $(SRC_API_DIR)/3.xml 3 \
@@ -1057,7 +1061,7 @@
     -since $(SRC_API_DIR)/25.txt 25 \
     -since $(SRC_API_DIR)/26.txt 26 \
     -since $(SRC_API_DIR)/27.txt 27 \
-    -werror -lerror -hide 111 -hide 113 -hide 121 -hide 125 -hide 126 -hide 127 -hide 128 \
+    -werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 \
     -overview $(LOCAL_PATH)/core/java/overview.html \
 
 framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \
@@ -1112,7 +1116,7 @@
 		-federate SupportLib https://developer.android.com \
 		-federationapi SupportLib prebuilts/sdk/current/support-api.txt
 
-# ====  the api diff ===========================
+# ====  Public API diff ===========================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(framework_docs_LOCAL_API_CHECK_SRC_FILES)
@@ -1137,6 +1141,31 @@
 
 include $(BUILD_APIDIFF)
 
+# ====  System API diff ===========================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(framework_docs_LOCAL_API_CHECK_SRC_FILES)
+LOCAL_INTERMEDIATE_SOURCES := $(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_JAVA_LIBRARIES := $(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
+LOCAL_MODULE_CLASS := $(framework_docs_LOCAL_MODULE_CLASS)
+LOCAL_ADDITIONAL_JAVA_DIR := $(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES := \
+	$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES) \
+	$(INTERNAL_PLATFORM_SYSTEM_API_FILE)
+
+LOCAL_MODULE := offline-system-sdk-referenceonly
+
+last_released_sdk_version := $(lastword $(call numerically_sort, \
+			$(filter-out current, \
+				$(patsubst $(SRC_SYSTEM_API_DIR)/%.txt,%, $(wildcard $(SRC_SYSTEM_API_DIR)/*.txt)) \
+			 )\
+		))
+
+LOCAL_APIDIFF_OLDAPI := $(LOCAL_PATH)/../../$(SRC_SYSTEM_API_DIR)/$(last_released_sdk_version)
+LOCAL_APIDIFF_NEWAPI := $(LOCAL_PATH)/../../$(basename $(INTERNAL_PLATFORM_SYSTEM_API_FILE))
+
+include $(BUILD_APIDIFF)
+
 # ====  the api stubs and current.xml ===========================
 include $(CLEAR_VARS)
 
@@ -1313,7 +1342,7 @@
 $(full_target): $(framework_built)
 
 
-# ====  static html in the sdk ==================================
+# ==== Public API static reference docs ==================================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
@@ -1355,6 +1384,50 @@
 $(full_target): $(framework_built)
 
 
+# ==== System API static reference docs ==================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
+LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
+LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
+LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
+LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
+LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
+
+LOCAL_MODULE := offline-system-sdk-referenceonly
+
+LOCAL_DROIDDOC_OPTIONS:=\
+		$(framework_docs_LOCAL_DROIDDOC_OPTIONS) \
+		-hide 101 -hide 104 -hide 108 \
+		-showAnnotation android.annotation.SystemApi \
+		-offlinemode \
+		-title "Android System SDK" \
+		-proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
+		-sdkvalues $(OUT_DOCS) \
+		-hdf android.whichdoc offline \
+		-referenceonly
+
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=external/doclava/res/assets/templates-sdk
+
+include $(BUILD_DROIDDOC)
+
+static_doc_index_redirect := $(out_dir)/index.html
+$(static_doc_index_redirect): $(LOCAL_PATH)/docs/docs-documentation-redirect.html
+	$(copy-file-to-target)
+
+static_doc_properties := $(out_dir)/source.properties
+$(static_doc_properties): \
+	$(LOCAL_PATH)/docs/source.properties | $(ACP)
+	$(hide) mkdir -p $(dir $@)
+	$(hide) $(ACP) $< $@
+
+$(full_target): $(static_doc_index_redirect)
+$(full_target): $(static_doc_properties)
+$(full_target): $(framework_built)
+
+
 # ==== docs for the web (on the androiddevdocs app engine server) =======================
 include $(CLEAR_VARS)
 
diff --git a/api/current.txt b/api/current.txt
index c1188dc..ed14b0e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -343,6 +343,7 @@
     field public static final int buttonBarNeutralButtonStyle = 16843914; // 0x101048a
     field public static final int buttonBarPositiveButtonStyle = 16843913; // 0x1010489
     field public static final int buttonBarStyle = 16843566; // 0x101032e
+    field public static final int buttonCornerRadius = 16844149; // 0x1010575
     field public static final int buttonGravity = 16844030; // 0x10104fe
     field public static final int buttonStyle = 16842824; // 0x1010048
     field public static final int buttonStyleInset = 16842826; // 0x101004a
@@ -1119,6 +1120,7 @@
     field public static final int scheme = 16842791; // 0x1010027
     field public static final int screenDensity = 16843467; // 0x10102cb
     field public static final int screenOrientation = 16842782; // 0x101001e
+    field public static final int screenReaderFocusable = 16844148; // 0x1010574
     field public static final int screenSize = 16843466; // 0x10102ca
     field public static final int scrollHorizontally = 16843099; // 0x101015b
     field public static final int scrollIndicators = 16844006; // 0x10104e6
@@ -2313,7 +2315,9 @@
     field public static final int Widget_DeviceDefault_AutoCompleteTextView = 16974151; // 0x1030147
     field public static final int Widget_DeviceDefault_Button = 16974145; // 0x1030141
     field public static final int Widget_DeviceDefault_Button_Borderless = 16974188; // 0x103016c
+    field public static final int Widget_DeviceDefault_Button_Borderless_Colored = 16974561; // 0x10302e1
     field public static final int Widget_DeviceDefault_Button_Borderless_Small = 16974149; // 0x1030145
+    field public static final int Widget_DeviceDefault_Button_Colored = 16974560; // 0x10302e0
     field public static final int Widget_DeviceDefault_Button_Inset = 16974147; // 0x1030143
     field public static final int Widget_DeviceDefault_Button_Small = 16974146; // 0x1030142
     field public static final int Widget_DeviceDefault_Button_Toggle = 16974148; // 0x1030144
@@ -6588,6 +6592,7 @@
 
   public abstract class NetworkEvent implements android.os.Parcelable {
     method public int describeContents();
+    method public long getId();
     method public java.lang.String getPackageName();
     method public long getTimestamp();
     field public static final android.os.Parcelable.Creator<android.app.admin.NetworkEvent> CREATOR;
@@ -22028,6 +22033,7 @@
     method public android.media.Image acquireLatestImage();
     method public android.media.Image acquireNextImage();
     method public void close();
+    method public void discardFreeBuffers();
     method public int getHeight();
     method public int getImageFormat();
     method public int getMaxImages();
@@ -23252,16 +23258,20 @@
     field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
   }
 
-  public class MediaRecorder {
+  public class MediaRecorder implements android.media.AudioRouting {
     ctor public MediaRecorder();
+    method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method protected void finalize();
     method public static final int getAudioSourceMax();
     method public int getMaxAmplitude() throws java.lang.IllegalStateException;
     method public android.os.PersistableBundle getMetrics();
+    method public android.media.AudioDeviceInfo getPreferredDevice();
+    method public android.media.AudioDeviceInfo getRoutedDevice();
     method public android.view.Surface getSurface();
     method public void pause() throws java.lang.IllegalStateException;
     method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
     method public void release();
+    method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public void reset();
     method public void resume() throws java.lang.IllegalStateException;
     method public void setAudioChannels(int);
@@ -23284,6 +23294,7 @@
     method public void setOutputFile(java.io.File);
     method public void setOutputFile(java.lang.String) throws java.lang.IllegalStateException;
     method public void setOutputFormat(int) throws java.lang.IllegalStateException;
+    method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
     method public void setPreviewDisplay(android.view.Surface);
     method public void setProfile(android.media.CamcorderProfile);
     method public void setVideoEncoder(int) throws java.lang.IllegalStateException;
@@ -37499,6 +37510,7 @@
   public final class SaveCallback {
     method public void onFailure(java.lang.CharSequence);
     method public void onSuccess();
+    method public void onSuccess(android.content.IntentSender);
   }
 
   public final class SaveInfo implements android.os.Parcelable {
@@ -37555,6 +37567,7 @@
 
   public final class Validators {
     method public static android.service.autofill.Validator and(android.service.autofill.Validator...);
+    method public static android.service.autofill.Validator not(android.service.autofill.Validator);
     method public static android.service.autofill.Validator or(android.service.autofill.Validator...);
   }
 
@@ -41768,8 +41781,6 @@
   public deprecated class TestSuiteBuilder {
     ctor public TestSuiteBuilder(java.lang.Class);
     ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader);
-    method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>);
-    method public final android.test.suitebuilder.TestSuiteBuilder addRequirements(com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>...);
     method public final junit.framework.TestSuite build();
     method public android.test.suitebuilder.TestSuiteBuilder excludePackages(java.lang.String...);
     method protected java.lang.String getSuiteName();
@@ -46330,6 +46341,7 @@
     method public boolean isPressed();
     method public boolean isSaveEnabled();
     method public boolean isSaveFromParentEnabled();
+    method public boolean isScreenReaderFocusable();
     method public boolean isScrollContainer();
     method public boolean isScrollbarFadingEnabled();
     method public boolean isSelected();
@@ -46549,6 +46561,7 @@
     method public void setSaveFromParentEnabled(boolean);
     method public void setScaleX(float);
     method public void setScaleY(float);
+    method public void setScreenReaderFocusable(boolean);
     method public void setScrollBarDefaultDelayBeforeFade(int);
     method public void setScrollBarFadeDuration(int);
     method public void setScrollBarSize(int);
@@ -47894,6 +47907,7 @@
     method public void interrupt();
     method public static boolean isAccessibilityButtonSupported();
     method public boolean isEnabled();
+    method public boolean isObservedEventType(int);
     method public boolean isTouchExplorationEnabled();
     method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
     method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
@@ -47967,6 +47981,7 @@
     method public boolean isLongClickable();
     method public boolean isMultiLine();
     method public boolean isPassword();
+    method public boolean isScreenReaderFocusable();
     method public boolean isScrollable();
     method public boolean isSelected();
     method public boolean isShowingHintText();
@@ -48022,6 +48037,7 @@
     method public void setParent(android.view.View, int);
     method public void setPassword(boolean);
     method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo);
+    method public void setScreenReaderFocusable(boolean);
     method public void setScrollable(boolean);
     method public void setSelected(boolean);
     method public void setShowingHintText(boolean);
@@ -49077,15 +49093,19 @@
 
   public abstract interface TextClassifier {
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
+    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int);
     method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
     method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
+    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
+    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final android.view.textclassifier.TextClassifier NO_OP;
     field public static final java.lang.String TYPE_ADDRESS = "address";
     field public static final java.lang.String TYPE_EMAIL = "email";
     field public static final java.lang.String TYPE_OTHER = "other";
     field public static final java.lang.String TYPE_PHONE = "phone";
+    field public static final java.lang.String TYPE_UNKNOWN = "";
     field public static final java.lang.String TYPE_URL = "url";
   }
 
@@ -49101,13 +49121,9 @@
   }
 
   public static final class TextLinks.Options {
+    ctor public TextLinks.Options();
     method public android.os.LocaleList getDefaultLocales();
-  }
-
-  public static final class TextLinks.Options.Builder {
-    ctor public TextLinks.Options.Builder();
-    method public android.view.textclassifier.TextLinks.Options build();
-    method public android.view.textclassifier.TextLinks.Options.Builder setLocaleList(android.os.LocaleList);
+    method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList);
   }
 
   public static final class TextLinks.TextLink {
diff --git a/api/removed.txt b/api/removed.txt
index 2934846..be4d5be 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -210,10 +210,6 @@
 
 package android.media.tv {
 
-  public final class TvInputManager {
-    method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo);
-  }
-
   public class TvView extends android.view.ViewGroup {
     method public void requestUnblockContent(android.media.tv.TvContentRating);
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 990df9b..95f28e4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -751,6 +751,20 @@
     field public java.lang.String credentialProtectedDataDir;
   }
 
+  public final class InstantAppInfo implements android.os.Parcelable {
+    ctor public InstantAppInfo(android.content.pm.ApplicationInfo, java.lang.String[], java.lang.String[]);
+    ctor public InstantAppInfo(java.lang.String, java.lang.CharSequence, java.lang.String[], java.lang.String[]);
+    method public int describeContents();
+    method public android.content.pm.ApplicationInfo getApplicationInfo();
+    method public java.lang.String[] getGrantedPermissions();
+    method public java.lang.String getPackageName();
+    method public java.lang.String[] getRequestedPermissions();
+    method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
+    method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.content.pm.InstantAppInfo> CREATOR;
+  }
+
   public final class InstantAppIntentFilter implements android.os.Parcelable {
     ctor public InstantAppIntentFilter(java.lang.String, java.util.List<android.content.IntentFilter>);
     method public int describeContents();
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 396bcd6..f98d011 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -103,6 +103,10 @@
 
 package android.media.tv {
 
+  public final class TvInputManager {
+    method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo);
+  }
+
   public static final class TvInputManager.Hardware {
     method public boolean dispatchKeyEventToHdmi(android.view.KeyEvent);
   }
@@ -111,6 +115,13 @@
 
 package android.net.wifi {
 
+  public deprecated class BatchedScanResult implements android.os.Parcelable {
+    ctor public BatchedScanResult();
+    ctor public BatchedScanResult(android.net.wifi.BatchedScanResult);
+    field public final java.util.List<android.net.wifi.ScanResult> scanResults;
+    field public boolean truncated;
+  }
+
   public class ScanResult implements android.os.Parcelable {
     field public boolean untrusted;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 8647ed3..b181538 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -958,6 +958,15 @@
 
 package android.view.accessibility {
 
+  public final class AccessibilityManager {
+    method public void addAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, android.os.Handler);
+    method public void removeAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
+  }
+
+  public static abstract interface AccessibilityManager.AccessibilityServicesStateChangeListener {
+    method public abstract void onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager);
+  }
+
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
   }
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 4d975fc..1f15c5e 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -57,7 +57,8 @@
     src/StatsLogProcessor.cpp \
     src/StatsService.cpp \
     src/stats_util.cpp \
-    src/guardrail/MemoryLeakTrackUtil.cpp
+    src/guardrail/MemoryLeakTrackUtil.cpp \
+    src/guardrail/StatsdStats.cpp
 
 statsd_common_c_includes := \
     $(LOCAL_PATH)/src \
@@ -167,7 +168,8 @@
     tests/metrics/MaxDurationTracker_test.cpp \
     tests/metrics/CountMetricProducer_test.cpp \
     tests/metrics/EventMetricProducer_test.cpp \
-    tests/metrics/ValueMetricProducer_test.cpp
+    tests/metrics/ValueMetricProducer_test.cpp \
+    tests/guardrail/StatsdStats_test.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     libgmock
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index b5cb20c..2fd7947 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 #include "statslog.h"
 
@@ -21,6 +22,7 @@
 #include <dirent.h>
 #include "StatsLogProcessor.h"
 #include "android-base/stringprintf.h"
+#include "guardrail/StatsdStats.h"
 #include "metrics/CountMetricProducer.h"
 #include "stats_util.h"
 #include "storage/StorageManager.h"
@@ -73,15 +75,14 @@
 void StatsLogProcessor::onAnomalyAlarmFired(
         const uint64_t timestampNs,
         unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
-    for (const auto& anomaly : anomalySet) {
-        for (const auto& itr : mMetricsManagers) {
-            itr.second->onAnomalyAlarmFired(timestampNs, anomaly);
-        }
+    for (const auto& itr : mMetricsManagers) {
+        itr.second->onAnomalyAlarmFired(timestampNs, anomalySet);
     }
 }
 
 // TODO: what if statsd service restarts? How do we know what logs are already processed before?
 void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
+    StatsdStats::getInstance().noteAtomLogged(msg.GetTagId(), msg.GetTimestampNs() / NS_PER_SEC);
     // pass the event to metrics managers.
     for (auto& pair : mMetricsManagers) {
         pair.second->onLogEvent(msg);
@@ -106,23 +107,26 @@
 }
 
 void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
+    ALOGD("Updated configuration for key %s", key.ToString().c_str());
+    unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(key, config);
+
     auto it = mMetricsManagers.find(key);
     if (it != mMetricsManagers.end()) {
         it->second->finish();
+    } else if (mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
+        ALOGE("Can't accept more configs!");
+        return;
     }
 
-    ALOGD("Updated configuration for key %s", key.ToString().c_str());
-
-    unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
     if (newMetricsManager->isConfigValid()) {
         mUidMap->OnConfigUpdated(key);
         newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
         mMetricsManagers[key] = std::move(newMetricsManager);
         // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
-        ALOGD("StatsdConfig valid");
+        VLOG("StatsdConfig valid");
     } else {
         // If there is any error in the config, don't use it.
-        ALOGD("StatsdConfig NOT valid");
+        ALOGE("StatsdConfig NOT valid");
     }
 }
 
@@ -195,6 +199,7 @@
             iter.rp()->move(toRead);
         }
     }
+    StatsdStats::getInstance().noteMetricsReportSent(key);
 }
 
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
@@ -204,6 +209,7 @@
         mMetricsManagers.erase(it);
         mUidMap->OnConfigRemoved(key);
     }
+    StatsdStats::getInstance().noteConfigRemoved(key);
 
     std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
     mLastBroadcastTimes.erase(key);
@@ -214,7 +220,7 @@
                                          const unique_ptr<MetricsManager>& metricsManager) {
     std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
 
-    size_t totalBytes = metricsManager->byteSize();
+    size_t totalBytes = metricsManager->byteSize() + mUidMap->getBytesUsed();
     if (totalBytes > .9 * kMaxSerializedBytes) { // Send broadcast so that receivers can pull data.
         auto lastFlushNs = mLastBroadcastTimes.find(key);
         if (lastFlushNs != mLastBroadcastTimes.end()) {
@@ -223,12 +229,14 @@
             }
         }
         mLastBroadcastTimes[key] = timestampNs;
-        ALOGD("StatsD requesting broadcast for %s", key.ToString().c_str());
+        VLOG("StatsD requesting broadcast for %s", key.ToString().c_str());
         mSendBroadcast(key);
+        StatsdStats::getInstance().noteBroadcastSent(key);
     } else if (totalBytes > kMaxSerializedBytes) { // Too late. We need to start clearing data.
         // We ignore the return value so we force each metric producer to clear its contents.
         metricsManager->onDumpReport();
-        ALOGD("StatsD had to toss out metrics for %s", key.ToString().c_str());
+        StatsdStats::getInstance().noteDataDropped(key);
+        VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
     }
 }
 
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 57928d0..27e3854 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -44,6 +44,8 @@
     size_t GetMetricsSize(const ConfigKey& key) const;
 
     void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
+
+    /* Tells MetricsManager that the alarms in anomalySet have fired. Modifies anomalySet. */
     void onAnomalyAlarmFired(
             const uint64_t timestampNs,
             unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index ec2650e..7eca5aa 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG true
+#define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
 #include "StatsService.h"
@@ -22,6 +22,7 @@
 #include "config/ConfigKey.h"
 #include "config/ConfigManager.h"
 #include "guardrail/MemoryLeakTrackUtil.h"
+#include "guardrail/StatsdStats.h"
 #include "storage/DropboxReader.h"
 #include "storage/StorageManager.h"
 
@@ -79,10 +80,10 @@
         sp<IStatsCompanionService> sc = getStatsCompanionService();
         auto receiver = mConfigManager->GetConfigReceiver(key);
         if (sc == nullptr) {
-            ALOGD("Could not find StatsCompanionService");
+            VLOG("Could not find StatsCompanionService");
         } else if (receiver.first.size() == 0) {
-            ALOGD("Statscompanion could not find a broadcast receiver for %s",
-                  key.ToString().c_str());
+            VLOG("Statscompanion could not find a broadcast receiver for %s",
+                 key.ToString().c_str());
         } else {
             sc->sendBroadcast(String16(receiver.first.c_str()),
                               String16(receiver.second.c_str()));
@@ -222,11 +223,7 @@
         }
 
         if (!args[0].compare(String8("print-stats"))) {
-            return cmd_print_stats(out);
-        }
-
-        if (!args[0].compare(String8("clear-config"))) {
-            return cmd_remove_config_files(out);
+            return cmd_print_stats(out, args);
         }
 
         if (!args[0].compare(String8("meminfo"))) {
@@ -262,11 +259,6 @@
     fprintf(out, "  Prints the UID, app name, version mapping.\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
-    fprintf(out, "usage: adb shell cmd stats clear-config \n");
-    fprintf(out, "\n");
-    fprintf(out, "  Removes all configs from disk.\n");
-    fprintf(out, "\n");
-    fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmd stats pull-source [int] \n");
     fprintf(out, "\n");
     fprintf(out, "  Prints the output of a pulled metrics source (int indicates source)\n");
@@ -277,17 +269,20 @@
     fprintf(out, "  Flushes all data on memory to disk.\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
-    fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n");
+    fprintf(out, "usage: adb shell cmd stats config remove [UID] [NAME]\n");
     fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
     fprintf(out, "\n");
     fprintf(out, "  Adds, updates or removes a configuration. The proto should be in\n");
-    fprintf(out, "  wire-encoded protobuf format and passed via stdin.\n");
+    fprintf(out, "  wire-encoded protobuf format and passed via stdin. If no UID and name is\n");
+    fprintf(out, "  provided, then all configs will be removed from memory and disk.\n");
     fprintf(out, "\n");
     fprintf(out, "  UID           The uid to use. It is only possible to pass the UID\n");
-    fprintf(out, "                parameter on eng builds.  If UID is omitted the calling\n");
+    fprintf(out, "                parameter on eng builds. If UID is omitted the calling\n");
     fprintf(out, "                uid is used.\n");
     fprintf(out, "  NAME          The per-uid name to use\n");
     fprintf(out, "\n");
+    fprintf(out, "\n              *Note: If both UID and NAME are omitted then all configs will\n");
+    fprintf(out, "\n                     be removed from memory and disk!\n");
     fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmd stats dump-report [UID] NAME\n");
     fprintf(out, "  Dump all metric data for a configuration.\n");
@@ -305,8 +300,9 @@
     fprintf(out, "  NAME          The name of the configuration\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
-    fprintf(out, "usage: adb shell cmd stats print-stats\n");
+    fprintf(out, "usage: adb shell cmd stats print-stats [reset]\n");
     fprintf(out, "  Prints some basic stats.\n");
+    fprintf(out, "  reset: 1 to reset the statsd stats. default=0.\n");
 }
 
 status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) {
@@ -347,10 +343,10 @@
     sp<IStatsCompanionService> sc = getStatsCompanionService();
     if (sc != nullptr) {
         sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str()));
-        ALOGD("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
-              args[2].c_str());
+        VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
+             args[2].c_str());
     } else {
-        ALOGD("Could not access statsCompanion");
+        VLOG("Could not access statsCompanion");
     }
 
     return NO_ERROR;
@@ -414,8 +410,12 @@
                 // Add / update the config.
                 mConfigManager->UpdateConfig(ConfigKey(uid, name), config);
             } else {
-                // Remove the config.
-                mConfigManager->RemoveConfig(ConfigKey(uid, name));
+                if (argCount == 2) {
+                    cmd_remove_all_configs(out);
+                } else {
+                    // Remove the config.
+                    mConfigManager->RemoveConfig(ConfigKey(uid, name));
+                }
             }
 
             return NO_ERROR;
@@ -474,12 +474,20 @@
     }
 }
 
-status_t StatsService::cmd_print_stats(FILE* out) {
+status_t StatsService::cmd_print_stats(FILE* out, const Vector<String8>& args) {
     vector<ConfigKey> configs = mConfigManager->GetAllConfigKeys();
     for (const ConfigKey& key : configs) {
         fprintf(out, "Config %s uses %zu bytes\n", key.ToString().c_str(),
                 mProcessor->GetMetricsSize(key));
     }
+    fprintf(out, "Detailed statsd stats in logcat...\n");
+    StatsdStats& statsdStats = StatsdStats::getInstance();
+    bool reset = false;
+    if (args.size() > 1) {
+        reset = strtol(args[1].string(), NULL, 10);
+    }
+    vector<uint8_t> output;
+    statsdStats.dumpStats(&output, reset);
     return NO_ERROR;
 }
 
@@ -516,8 +524,10 @@
     return UNKNOWN_ERROR;
 }
 
-status_t StatsService::cmd_remove_config_files(FILE* out) {
-    fprintf(out, "Trying to remove config files...\n");
+status_t StatsService::cmd_remove_all_configs(FILE* out) {
+    fprintf(out, "Removing all configs...\n");
+    VLOG("StatsService::cmd_remove_all_configs was called");
+    mConfigManager->RemoveAllConfigs();
     StorageManager::deleteAllFiles(STATS_SERVICE_DIR);
     return NO_ERROR;
 }
@@ -531,7 +541,7 @@
 
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
                                       const vector<String16>& app) {
-    if (DEBUG) ALOGD("StatsService::informAllUidData was called");
+    VLOG("StatsService::informAllUidData was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -539,13 +549,13 @@
     }
 
     mUidMap->updateMap(uid, version, app);
-    if (DEBUG) ALOGD("StatsService::informAllUidData succeeded");
+    VLOG("StatsService::informAllUidData succeeded");
 
     return Status::ok();
 }
 
 Status StatsService::informOnePackage(const String16& app, int32_t uid, int32_t version) {
-    if (DEBUG) ALOGD("StatsService::informOnePackage was called");
+    VLOG("StatsService::informOnePackage was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -556,7 +566,7 @@
 }
 
 Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
-    if (DEBUG) ALOGD("StatsService::informOnePackageRemoved was called");
+    VLOG("StatsService::informOnePackageRemoved was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -567,24 +577,23 @@
 }
 
 Status StatsService::informAnomalyAlarmFired() {
-    if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired was called");
+    VLOG("StatsService::informAnomalyAlarmFired was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
                                          "Only system uid can call informAnomalyAlarmFired");
     }
 
-    if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
-    // TODO: check through all counters/timers and see if an anomaly has indeed occurred.
-    uint64_t currentTimeNs = time(nullptr) * NS_PER_SEC;
+    VLOG("StatsService::informAnomalyAlarmFired succeeded");
+    uint64_t currentTimeSec = time(nullptr);
     std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
-            mAnomalyMonitor->onAlarmFired(currentTimeNs);
-    mProcessor->onAnomalyAlarmFired(currentTimeNs, anomalySet);
+            mAnomalyMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+    mProcessor->onAnomalyAlarmFired(currentTimeSec * NS_PER_SEC, anomalySet);
     return Status::ok();
 }
 
 Status StatsService::informPollAlarmFired() {
-    if (DEBUG) ALOGD("StatsService::informPollAlarmFired was called");
+    VLOG("StatsService::informPollAlarmFired was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -593,7 +602,7 @@
 
     mStatsPullerManager.OnAlarmFired();
 
-    if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
+    VLOG("StatsService::informPollAlarmFired succeeded");
 
     return Status::ok();
 }
@@ -605,7 +614,7 @@
     }
 
     // When system_server is up and running, schedule the dropbox task to run.
-    ALOGD("StatsService::systemRunning");
+    VLOG("StatsService::systemRunning");
 
     sayHiToStatsCompanion();
 
@@ -618,7 +627,7 @@
                                          "Only system uid can call systemRunning");
     }
 
-    ALOGD("StatsService::writeDataToDisk");
+    VLOG("StatsService::writeDataToDisk");
 
     mProcessor->WriteDataToDisk();
 
@@ -630,10 +639,10 @@
     // purposes.
     sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion != nullptr) {
-        if (DEBUG) ALOGD("Telling statsCompanion that statsd is ready");
+        VLOG("Telling statsCompanion that statsd is ready");
         statsCompanion->statsdReady();
     } else {
-        if (DEBUG) ALOGD("Could not access statsCompanion");
+        VLOG("Could not access statsCompanion");
     }
 }
 
@@ -653,7 +662,7 @@
 }
 
 Status StatsService::statsCompanionReady() {
-    if (DEBUG) ALOGD("StatsService::statsCompanionReady was called");
+    VLOG("StatsService::statsCompanionReady was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -666,7 +675,7 @@
                 Status::EX_NULL_POINTER,
                 "statscompanion unavailable despite it contacting statsd!");
     }
-    if (DEBUG) ALOGD("StatsService::statsCompanionReady linking to statsCompanion.");
+    VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
     IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
     mAnomalyMonitor->setStatsCompanionService(statsCompanion);
 
@@ -683,8 +692,7 @@
 
 Status StatsService::getData(const String16& key, vector<uint8_t>* output) {
     IPCThreadState* ipc = IPCThreadState::self();
-    ALOGD("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(),
-          ipc->getCallingUid());
+    VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
     if (checkCallingPermission(String16(kPermissionDump))) {
         string keyStr = string(String8(key).string());
         ConfigKey configKey(ipc->getCallingUid(), keyStr);
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 6b5c156..007227e 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -134,7 +134,7 @@
     /**
      * Prints some basic stats to std out.
      */
-    status_t cmd_print_stats(FILE* out);
+    status_t cmd_print_stats(FILE* out, const Vector<String8>& args);
 
     /**
      * Print the event log.
@@ -162,9 +162,9 @@
     status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
 
     /**
-     * Removes all configs stored on disk.
+     * Removes all configs stored on disk and on memory.
      */
-    status_t cmd_remove_config_files(FILE* out);
+    status_t cmd_remove_all_configs(FILE* out);
 
     /*
      * Dump memory usage by statsd.
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
index da52a9d..2b2bcfc 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -72,7 +72,8 @@
         return;
     }
     if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec);
-    mPq.remove(alarm);
+    bool wasPresent = mPq.remove(alarm);
+    if (!wasPresent) return;
     if (mPq.empty()) {
         if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
         mRegisteredAlarmTimeSec = 0;
@@ -129,11 +130,6 @@
     return ((int64_t)timeSec) * 1000;
 }
 
-unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::onAlarmFired(
-        uint64_t timestampNs) {
-    return popSoonerThan(static_cast<uint32_t>(timestampNs));
-}
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index 0bd5055..e19c469 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -23,7 +23,6 @@
 
 #include <queue>
 #include <set>
-#include <unordered_map>
 #include <unordered_set>
 #include <vector>
 
@@ -97,15 +96,6 @@
     unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> popSoonerThan(
             uint32_t timestampSec);
 
-    // TODO: Function that uses popSoonerThan to get all alarms that have fired, and then
-    // iterates over all DurationAnomalyTracker, looking for those alarms. When they're found,
-    // have them declareAnomaly on those alarms. This means that DurationAnomalyTracker
-    // must be thread-safe (since this is being called on a different thread). There is no
-    // worry about missing the alarms (due to them being cancelled after this function being called)
-    // because DurationAnomalyTracker guarantees that it checks for anaomlies when it cancels
-    // alarms anyway.
-    // void declareAnomalies(uint32_t timestampSec);
-
     /**
      * Returns the projected alarm timestamp that is registered with
      * StatsCompanionService. This may not be equal to the soonest alarm,
@@ -115,8 +105,6 @@
         return mRegisteredAlarmTimeSec;
     }
 
-    unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> onAlarmFired(uint64_t timestampNs);
-
 private:
     std::mutex mLock;
 
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 0904a04..7bacb44 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -19,32 +19,32 @@
 
 #include "AnomalyTracker.h"
 
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IServiceManager.h>
 #include <time.h>
 
 namespace android {
 namespace os {
 namespace statsd {
 
-AnomalyTracker::AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs)
+// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer
+//       decide and let which one it wants.
+// TODO: Get rid of bucketNumbers, and return to the original circular array method.
+AnomalyTracker::AnomalyTracker(const Alert& alert)
     : mAlert(alert),
-      mBucketSizeNs(bucketSizeNs),
-      mNumOfPastPackets(mAlert.number_of_buckets() - 1) {
+      mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
     VLOG("AnomalyTracker() called");
     if (mAlert.number_of_buckets() <= 0) {
-        ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
+        ALOGE("Cannot create AnomalyTracker with %lld buckets",
               (long long)mAlert.number_of_buckets());
         return;
     }
-    if (mBucketSizeNs <= 0) {
-        ALOGE("Cannot create DiscreteAnomalyTracker with bucket size %lld ",
-              (long long)mBucketSizeNs);
-        return;
-    }
     if (!mAlert.has_trigger_if_sum_gt()) {
-        ALOGE("Cannot create DiscreteAnomalyTracker without threshold");
+        ALOGE("Cannot create AnomalyTracker without threshold");
         return;
     }
-    reset(); // initialization
+    resetStorage(); // initialization
 }
 
 AnomalyTracker::~AnomalyTracker() {
@@ -52,37 +52,36 @@
     stopAllAlarms();
 }
 
-void AnomalyTracker::reset() {
-    VLOG("reset() called.");
-    stopAllAlarms();
+void AnomalyTracker::resetStorage() {
+    VLOG("resetStorage() called.");
     mPastBuckets.clear();
     // Excludes the current bucket.
-    mPastBuckets.resize(mNumOfPastPackets);
+    mPastBuckets.resize(mNumOfPastBuckets);
     mSumOverPastBuckets.clear();
-    mMostRecentBucketNum = -1;
-    mLastAlarmTimestampNs = -1;
+
+    if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
 }
 
 size_t AnomalyTracker::index(int64_t bucketNum) const {
-    return bucketNum % mNumOfPastPackets;
+    return bucketNum % mNumOfPastBuckets;
 }
 
 void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
     VLOG("addPastBucket() called.");
-    if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastPackets) {
+    if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
         ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
         return;
     }
 
     // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
     // mSumOverPastBuckets.
-    if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastPackets) {
+    if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastBuckets) {
         mPastBuckets.clear();
-        mPastBuckets.resize(mNumOfPastPackets);
+        mPastBuckets.resize(mNumOfPastBuckets);
         mSumOverPastBuckets.clear();
     } else {
-        for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastPackets + 1));
-             i <= latestPastBucketNum - mNumOfPastPackets; i++) {
+        for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastBuckets + 1));
+             i <= latestPastBucketNum - mNumOfPastBuckets; i++) {
             const int idx = index(i);
             subtractBucketFromSum(mPastBuckets[idx]);
             mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
@@ -91,7 +90,7 @@
 
     // It is an update operation.
     if (latestPastBucketNum <= mMostRecentBucketNum &&
-        latestPastBucketNum > mMostRecentBucketNum - mNumOfPastPackets) {
+        latestPastBucketNum > mMostRecentBucketNum - mNumOfPastBuckets) {
         subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
     }
 }
@@ -190,61 +189,79 @@
     if (currentBucketNum > mMostRecentBucketNum + 1) {
         addPastBucket(key, 0, currentBucketNum - 1);
     }
-    return getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
+    return mAlert.has_trigger_if_sum_gt()
+            && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
 }
 
-void AnomalyTracker::declareAnomaly(const uint64_t& timestamp) {
-    if (mLastAlarmTimestampNs >= 0 &&
-        timestamp - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC) {
-        VLOG("Skipping anomaly check since within refractory period");
+void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) {
+    // TODO: This should also take in the const HashableDimensionKey& key, to pass
+    //       more details to incidentd and to make mRefractoryPeriodEndsSec key-specific.
+    // TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
+    if (isInRefractoryPeriod(timestampNs)) {
+        VLOG("Skipping anomaly declaration since within refractory period");
         return;
     }
     // TODO(guardrail): Consider guarding against too short refractory periods.
-    mLastAlarmTimestampNs = timestamp;
+    mLastAlarmTimestampNs = timestampNs;
+
+
+    // TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
+    // if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
 
     if (mAlert.has_incidentd_details()) {
-        // TODO: Can construct a name based on the criteria (and/or relay the criteria).
-        ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
-        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+        if (mAlert.has_name()) {
+            ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
+                  mAlert.name().c_str());
+        } else {
+            // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+            ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+        }
+        informIncidentd();
     } else {
         ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
     }
 }
 
 void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
-                                                  const uint64_t& timestamp) {
+                                                  const uint64_t& timestampNs) {
     auto itr = mAlarms.find(dimensionKey);
     if (itr == mAlarms.end()) {
         return;
     }
 
     if (itr->second != nullptr &&
-        static_cast<uint32_t>(timestamp / NS_PER_SEC) >= itr->second->timestampSec) {
-        declareAnomaly(timestamp);
+        static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
+        declareAnomaly(timestampNs);
         stopAlarm(dimensionKey);
     }
 }
 
-void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
                                              const int64_t& currBucketNum,
                                              const HashableDimensionKey& key,
                                              const int64_t& currentBucketValue) {
     if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
-        declareAnomaly(timestamp);
+        declareAnomaly(timestampNs);
     }
 }
 
-void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
                                              const int64_t& currBucketNum,
                                              const DimToValMap& currentBucket) {
     if (detectAnomaly(currBucketNum, currentBucket)) {
-        declareAnomaly(timestamp);
+        declareAnomaly(timestampNs);
     }
 }
 
 void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
-                                const uint64_t& timestamp) {
-    sp<const AnomalyAlarm> alarm = new AnomalyAlarm{static_cast<uint32_t>(timestamp / NS_PER_SEC)};
+                                const uint64_t& timestampNs) {
+    uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
+    if (isInRefractoryPeriod(timestampNs)) {
+        VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
+        return;
+    }
+
+    sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
     mAlarms.insert({dimensionKey, alarm});
     if (mAnomalyMonitor != nullptr) {
         mAnomalyMonitor->add(alarm);
@@ -255,9 +272,9 @@
     auto itr = mAlarms.find(dimensionKey);
     if (itr != mAlarms.end()) {
         mAlarms.erase(dimensionKey);
-    }
-    if (mAnomalyMonitor != nullptr) {
-        mAnomalyMonitor->remove(itr->second);
+        if (mAnomalyMonitor != nullptr) {
+            mAnomalyMonitor->remove(itr->second);
+        }
     }
 }
 
@@ -271,6 +288,58 @@
     }
 }
 
+bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) {
+    return mLastAlarmTimestampNs >= 0 &&
+            timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
+}
+
+void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
+        unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
+
+    if (firedAlarms.empty() || mAlarms.empty()) return;
+    // Find the intersection of firedAlarms and mAlarms.
+    // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
+    // seldomly called. The alternative would be having AnomalyAlarms store information about the
+    // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is
+    // rarely ever called.
+    unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
+    for (const auto& kv : mAlarms) {
+        if (firedAlarms.count(kv.second) > 0) {
+            matchedAlarms.insert({kv.first, kv.second});
+        }
+    }
+
+    // Now declare each of these alarms to have fired.
+    for (const auto& kv : matchedAlarms) {
+        declareAnomaly(timestampNs /* TODO: , kv.first */);
+        mAlarms.erase(kv.first);
+        firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
+    }
+}
+
+void AnomalyTracker::informIncidentd() {
+    VLOG("informIncidentd called.");
+    if (!mAlert.has_incidentd_details()) {
+        ALOGE("Attempted to call incidentd without any incidentd_details.");
+        return;
+    }
+    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
+            defaultServiceManager()->getService(android::String16("incident")));
+    if (service == NULL) {
+        ALOGW("Couldn't get the incident service.");
+        return;
+    }
+
+    IncidentReportArgs incidentReport;
+    const Alert::IncidentdDetails& details = mAlert.incidentd_details();
+    for (int i = 0; i < details.section_size(); i++) {
+        incidentReport.addSection(details.section(i));
+    }
+    // TODO: Pass in mAlert.name() into the addHeader?
+
+    service->reportIncident(incidentReport);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index ce6c995..49e8323 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -32,10 +32,10 @@
 using std::unordered_map;
 using std::shared_ptr;
 
-// This anomaly track assmues that all values are non-negative.
+// Does NOT allow negative values.
 class AnomalyTracker : public virtual RefBase {
 public:
-    AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs);
+    AnomalyTracker(const Alert& alert);
 
     virtual ~AnomalyTracker();
 
@@ -51,12 +51,12 @@
                        const int64_t& currentBucketValue);
 
     // Informs incidentd about the detected alert.
-    void declareAnomaly(const uint64_t& timestamp);
+    void declareAnomaly(const uint64_t& timestampNs);
 
     // Detects the alert and informs the incidentd when applicable.
-    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+    void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
                                  const DimToValMap& currentBucket);
-    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+    void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
                                  const HashableDimensionKey& key,
                                  const int64_t& currentBucketValue);
 
@@ -75,7 +75,7 @@
 
     // Declares the anomaly when the alarm expired given the current timestamp.
     void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
-                                      const uint64_t& timestamp);
+                                      const uint64_t& timestampNs);
 
     // Helper function to return the sum value of past buckets at given dimension.
     int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
@@ -93,20 +93,26 @@
         return mLastAlarmTimestampNs;
     }
 
-    inline int getNumOfPastPackets() const {
-        return mNumOfPastPackets;
+    inline int getNumOfPastBuckets() const {
+        return mNumOfPastBuckets;
     }
 
+    // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
+    // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
+    // TODO: This will actually be called from a different thread, so make it thread-safe!
+    // TODO: Consider having AnomalyMonitor have a reference to each relevant MetricProducer
+    //       instead of calling it from a chain starting at StatsLogProcessor.
+    void informAlarmsFired(const uint64_t& timestampNs,
+            unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms);
+
 protected:
     void flushPastBuckets(const int64_t& currBucketNum);
     // statsd_config.proto Alert message that defines this tracker.
     const Alert mAlert;
 
-    // Bucket duration in ns.
-    int64_t mBucketSizeNs = 0;
-
-    // The number of past packets to track in the anomaly detection.
-    int mNumOfPastPackets = 0;
+    // Number of past buckets. One less than the total number of buckets needed
+    // for the anomaly detection (since the current bucket is not in the past).
+    int mNumOfPastBuckets;
 
     // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
     // are still active.
@@ -134,11 +140,16 @@
     // and remove any items with value 0.
     void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
 
+    bool isInRefractoryPeriod(const uint64_t& timestampNs);
+
     // Calculates the corresponding bucket index within the circular array.
     size_t index(int64_t bucketNum) const;
 
-    // Resets all data. For use when all the data gets stale.
-    void reset();
+    // Resets all bucket data. For use when all the data gets stale.
+    void resetStorage();
+
+    // Informs the incident service that an anomaly has occurred.
+    void informIncidentd();
 
     FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
     FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
diff --git a/cmds/statsd/src/anomaly/indexed_priority_queue.h b/cmds/statsd/src/anomaly/indexed_priority_queue.h
index 4982d4b..99882d0 100644
--- a/cmds/statsd/src/anomaly/indexed_priority_queue.h
+++ b/cmds/statsd/src/anomaly/indexed_priority_queue.h
@@ -37,7 +37,7 @@
 /**
  * Min priority queue for generic type AA.
  * Unlike a regular priority queue, this class is also capable of removing interior elements.
- * @tparam Comparator must implement [bool operator()(sp< AA> a, sp< AA> b)], returning
+ * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
  *    whether a should be closer to the top of the queue than b.
  */
 template <class AA, class Comparator>
@@ -46,8 +46,11 @@
     indexed_priority_queue();
     /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */
     void push(sp<const AA> a);
-    /** Removes a from the priority queue. If not present or a==nullptr, does nothing. */
-    void remove(sp<const AA> a);
+    /*
+     * Removes a from the priority queue. If not present or a==nullptr, does nothing.
+     * Returns true if a had been present (and is now removed), else false.
+     */
+    bool remove(sp<const AA> a);
     /** Removes the top element, if there is one. */
     void pop();
     /** Removes all elements. */
@@ -97,17 +100,17 @@
 }
 
 template <class AA, class Comparator>
-void indexed_priority_queue<AA, Comparator>::remove(sp<const AA> a) {
-    if (a == nullptr) return;
-    if (!contains(a)) return;
+bool indexed_priority_queue<AA, Comparator>::remove(sp<const AA> a) {
+    if (a == nullptr) return false;
+    if (!contains(a)) return false;
     size_t idx = indices[a];
     if (idx >= pq.size()) {
-        return;
+        return false;
     }
     if (idx == size()) {  // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
         pq.pop_back();
         indices.erase(a);
-        return;
+        return true;
     }
     // move last element (guaranteed not to be at idx) to idx, then delete a
     sp<const AA> last_a = pq.back();
@@ -119,6 +122,8 @@
     // get the heap back in order (since the element at idx is not in order)
     sift_up(idx);
     sift_down(idx);
+
+    return true;
 }
 
 // The same as, but slightly more efficient than, remove(top()).
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index d8099b3..50cd130 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -18,6 +18,7 @@
 #include "Log.h"
 
 #include "SimpleConditionTracker.h"
+#include "guardrail/StatsdStats.h"
 
 #include <log/logprint.h>
 
@@ -32,9 +33,10 @@
 using std::vector;
 
 SimpleConditionTracker::SimpleConditionTracker(
-        const string& name, const int index, const SimpleCondition& simpleCondition,
+        const ConfigKey& key, const string& name, const int index,
+        const SimpleCondition& simpleCondition,
         const unordered_map<string, int>& trackerNameIndexMap)
-    : ConditionTracker(name, index) {
+    : ConditionTracker(name, index), mConfigKey(key) {
     VLOG("creating SimpleConditionTracker %s", mName.c_str());
     mCountNesting = simpleCondition.count_nesting();
 
@@ -126,6 +128,24 @@
     conditionCache[mIndex] = ConditionState::kFalse;
 }
 
+bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+    if (!mSliced || mSlicedConditionState.find(newKey) != mSlicedConditionState.end()) {
+        // if the condition is not sliced or the key is not new, we are good!
+        return false;
+    }
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mSlicedConditionState.size() + 1;
+        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mName, newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("Condition %s dropping data for dimension key %s", mName.c_str(), newKey.c_str());
+            return true;
+        }
+    }
+    return false;
+}
+
 void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
                                                   bool matchStart,
                                                   std::vector<ConditionState>& conditionCache,
@@ -133,6 +153,12 @@
     bool changed = false;
     auto outputIt = mSlicedConditionState.find(outputKey);
     ConditionState newCondition;
+    if (hitGuardRail(outputKey)) {
+        conditionChangedCache[mIndex] = false;
+        // Tells the caller it's evaluated.
+        conditionCache[mIndex] = ConditionState::kUnknown;
+        return;
+    }
     if (outputIt == mSlicedConditionState.end()) {
         // We get a new output key.
         newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index c287d8f..d21afd1 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -19,6 +19,7 @@
 
 #include <gtest/gtest_prod.h>
 #include "ConditionTracker.h"
+#include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
 
@@ -28,7 +29,7 @@
 
 class SimpleConditionTracker : public virtual ConditionTracker {
 public:
-    SimpleConditionTracker(const std::string& name, const int index,
+    SimpleConditionTracker(const ConfigKey& key, const std::string& name, const int index,
                            const SimpleCondition& simpleCondition,
                            const std::unordered_map<std::string, int>& trackerNameIndexMap);
 
@@ -50,6 +51,7 @@
                         std::vector<ConditionState>& conditionCache) const override;
 
 private:
+    const ConfigKey mConfigKey;
     // The index of the LogEventMatcher which defines the start.
     int mStartLogMatcherIndex;
 
@@ -75,6 +77,8 @@
                               std::vector<ConditionState>& conditionCache,
                               std::vector<bool>& changedCache);
 
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
     FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition);
     FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim);
     FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll);
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index ac2192c..4b7e49a 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -102,8 +102,8 @@
         // Remove from map
         if (it->first.GetUid() == uid) {
             removed.push_back(it->first);
-            it = mConfigs.erase(it);
             mConfigReceivers.erase(it->first);
+            it = mConfigs.erase(it);
         } else {
             it++;
         }
@@ -118,6 +118,28 @@
     }
 }
 
+void ConfigManager::RemoveAllConfigs() {
+    vector<ConfigKey> removed;
+
+    for (auto it = mConfigs.begin(); it != mConfigs.end();) {
+        // Remove from map
+        removed.push_back(it->first);
+        auto receiverIt = mConfigReceivers.find(it->first);
+        if (receiverIt != mConfigReceivers.end()) {
+            mConfigReceivers.erase(it->first);
+        }
+        it = mConfigs.erase(it);
+    }
+
+    // Remove separately so if they do anything in the callback they can't mess up our iteration.
+    for (auto& key : removed) {
+        // Tell everyone
+        for (auto& listener : mListeners) {
+            listener->OnConfigRemoved(key);
+        }
+    }
+}
+
 vector<ConfigKey> ConfigManager::GetAllConfigKeys() const {
     vector<ConfigKey> ret;
     for (auto it = mConfigs.cbegin(); it != mConfigs.cend(); ++it) {
@@ -210,6 +232,9 @@
     alert->set_number_of_buckets(6);
     alert->set_trigger_if_sum_gt(10);
     alert->set_refractory_period_secs(30);
+    Alert::IncidentdDetails* details = alert->mutable_incidentd_details();
+    details->add_section(12);
+    details->add_section(13);
 
     // Count process state changes, slice by uid.
     metric = config.add_count_metric();
@@ -226,6 +251,9 @@
     alert->set_number_of_buckets(4);
     alert->set_trigger_if_sum_gt(30);
     alert->set_refractory_period_secs(20);
+    details = alert->mutable_incidentd_details();
+    details->add_section(14);
+    details->add_section(15);
 
     // Count process state changes, slice by uid, while SCREEN_IS_OFF
     metric = config.add_count_metric();
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 578e5ac..90ea6c0 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -98,6 +98,11 @@
     void RemoveConfigs(int uid);
 
     /**
+     * Remove all of the configs from memory.
+     */
+    void RemoveAllConfigs();
+
+    /**
      * Text dump of our state for debugging.
      */
     void Dump(FILE* out);
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
new file mode 100644
index 0000000..2957457
--- /dev/null
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "StatsdStats.h"
+
+#include <android/util/ProtoOutputStream.h>
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::util::FIELD_COUNT_REPEATED;
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::FIELD_TYPE_STRING;
+using android::util::ProtoOutputStream;
+using std::lock_guard;
+using std::map;
+using std::string;
+using std::vector;
+
+const int FIELD_ID_BEGIN_TIME = 1;
+const int FIELD_ID_END_TIME = 2;
+const int FIELD_ID_CONFIG_STATS = 3;
+const int FIELD_ID_MATCHER_STATS = 4;
+const int FIELD_ID_CONDITION_STATS = 5;
+const int FIELD_ID_METRIC_STATS = 6;
+const int FIELD_ID_ATOM_STATS = 7;
+const int FIELD_ID_UIDMAP_STATS = 8;
+
+const int FIELD_ID_MATCHER_STATS_NAME = 1;
+const int FIELD_ID_MATCHER_STATS_COUNT = 2;
+
+const int FIELD_ID_CONDITION_STATS_NAME = 1;
+const int FIELD_ID_CONDITION_STATS_COUNT = 2;
+
+const int FIELD_ID_METRIC_STATS_NAME = 1;
+const int FIELD_ID_METRIC_STATS_COUNT = 2;
+
+const int FIELD_ID_ATOM_STATS_TAG = 1;
+const int FIELD_ID_ATOM_STATS_COUNT = 2;
+
+// TODO: add stats for pulled atoms.
+StatsdStats::StatsdStats() {
+    mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1);
+    mStartTimeSec = time(nullptr);
+}
+
+StatsdStats& StatsdStats::getInstance() {
+    static StatsdStats statsInstance;
+    return statsInstance;
+}
+
+void StatsdStats::noteConfigReceived(const ConfigKey& key, int metricsCount, int conditionsCount,
+                                     int matchersCount, int alertsCount, bool isValid) {
+    lock_guard<std::mutex> lock(mLock);
+    int32_t nowTimeSec = time(nullptr);
+
+    // If there is an existing config for the same key, icebox the old config.
+    noteConfigRemovedInternalLocked(key);
+
+    StatsdStatsReport_ConfigStats configStats;
+    configStats.set_uid(key.GetUid());
+    configStats.set_name(key.GetName());
+    configStats.set_creation_time_sec(nowTimeSec);
+    configStats.set_metric_count(metricsCount);
+    configStats.set_condition_count(conditionsCount);
+    configStats.set_matcher_count(matchersCount);
+    configStats.set_alert_count(alertsCount);
+    configStats.set_is_valid(isValid);
+
+    if (isValid) {
+        mConfigStats[key] = configStats;
+    } else {
+        configStats.set_deletion_time_sec(nowTimeSec);
+        mIceBox.push_back(configStats);
+    }
+}
+
+void StatsdStats::noteConfigRemovedInternalLocked(const ConfigKey& key) {
+    auto it = mConfigStats.find(key);
+    if (it != mConfigStats.end()) {
+        int32_t nowTimeSec = time(nullptr);
+        it->second.set_deletion_time_sec(nowTimeSec);
+        // Add condition stats, metrics stats, matcher stats
+        addSubStatsToConfig(key, it->second);
+        // Remove them after they are added to the config stats.
+        mMatcherStats.erase(key);
+        mMetricsStats.erase(key);
+        mConditionStats.erase(key);
+        mIceBox.push_back(it->second);
+        mConfigStats.erase(it);
+    }
+}
+
+void StatsdStats::noteConfigRemoved(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    noteConfigRemovedInternalLocked(key);
+}
+
+void StatsdStats::noteBroadcastSent(const ConfigKey& key) {
+    noteBroadcastSent(key, time(nullptr));
+}
+
+void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    if (it->second.broadcast_sent_time_sec_size() >= kMaxTimestampCount) {
+        auto timestampList = it->second.mutable_broadcast_sent_time_sec();
+        // This is O(N) operation. It shouldn't happen often, and N is only 20.
+        timestampList->erase(timestampList->begin());
+    }
+    it->second.add_broadcast_sent_time_sec(timeSec);
+}
+
+void StatsdStats::noteDataDropped(const ConfigKey& key) {
+    noteDataDropped(key, time(nullptr));
+}
+
+void StatsdStats::noteDataDropped(const ConfigKey& key, int32_t timeSec) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    if (it->second.data_drop_time_sec_size() >= kMaxTimestampCount) {
+        auto timestampList = it->second.mutable_data_drop_time_sec();
+        // This is O(N) operation. It shouldn't happen often, and N is only 20.
+        timestampList->erase(timestampList->begin());
+    }
+    it->second.add_data_drop_time_sec(timeSec);
+}
+
+void StatsdStats::noteMetricsReportSent(const ConfigKey& key) {
+    noteMetricsReportSent(key, time(nullptr));
+}
+
+void StatsdStats::noteMetricsReportSent(const ConfigKey& key, int32_t timeSec) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    if (it->second.dump_report_time_sec_size() >= kMaxTimestampCount) {
+        auto timestampList = it->second.mutable_dump_report_time_sec();
+        // This is O(N) operation. It shouldn't happen often, and N is only 20.
+        timestampList->erase(timestampList->begin());
+    }
+    it->second.add_dump_report_time_sec(timeSec);
+}
+
+void StatsdStats::noteUidMapDropped(int snapshots, int deltas) {
+    lock_guard<std::mutex> lock(mLock);
+    mUidMapStats.set_dropped_snapshots(mUidMapStats.dropped_snapshots() + snapshots);
+    mUidMapStats.set_dropped_changes(mUidMapStats.dropped_changes() + deltas);
+}
+
+void StatsdStats::setUidMapSnapshots(int snapshots) {
+    lock_guard<std::mutex> lock(mLock);
+    mUidMapStats.set_snapshots(snapshots);
+}
+
+void StatsdStats::setUidMapChanges(int changes) {
+    lock_guard<std::mutex> lock(mLock);
+    mUidMapStats.set_changes(changes);
+}
+
+void StatsdStats::setCurrentUidMapMemory(int bytes) {
+    lock_guard<std::mutex> lock(mLock);
+    mUidMapStats.set_bytes_used(bytes);
+}
+
+void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) {
+    lock_guard<std::mutex> lock(mLock);
+    // if name doesn't exist before, it will create the key with count 0.
+    auto& conditionSizeMap = mConditionStats[key];
+    if (size > conditionSizeMap[name]) {
+        conditionSizeMap[name] = size;
+    }
+}
+
+void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const string& name, int size) {
+    lock_guard<std::mutex> lock(mLock);
+    // if name doesn't exist before, it will create the key with count 0.
+    auto& metricsDimensionMap = mMetricsStats[key];
+    if (size > metricsDimensionMap[name]) {
+        metricsDimensionMap[name] = size;
+    }
+}
+
+void StatsdStats::noteMatcherMatched(const ConfigKey& key, const string& name) {
+    lock_guard<std::mutex> lock(mLock);
+    auto& matcherStats = mMatcherStats[key];
+    matcherStats[name]++;
+}
+
+void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) {
+    lock_guard<std::mutex> lock(mLock);
+
+    if (timeSec < mStartTimeSec) {
+        return;
+    }
+
+    if (atomId > android::util::kMaxPushedAtomId) {
+        ALOGW("not interested in atom %d", atomId);
+        return;
+    }
+
+    mPushedAtomStats[atomId]++;
+}
+
+void StatsdStats::reset() {
+    lock_guard<std::mutex> lock(mLock);
+    resetInternalLocked();
+}
+
+void StatsdStats::resetInternalLocked() {
+    // Reset the historical data, but keep the active ConfigStats
+    mStartTimeSec = time(nullptr);
+    mIceBox.clear();
+    mConditionStats.clear();
+    mMetricsStats.clear();
+    std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0);
+    mMatcherStats.clear();
+    for (auto& config : mConfigStats) {
+        config.second.clear_broadcast_sent_time_sec();
+        config.second.clear_data_drop_time_sec();
+        config.second.clear_dump_report_time_sec();
+        config.second.clear_matcher_stats();
+        config.second.clear_condition_stats();
+        config.second.clear_metric_stats();
+    }
+}
+
+void StatsdStats::addSubStatsToConfig(const ConfigKey& key,
+                                      StatsdStatsReport_ConfigStats& configStats) {
+    // Add matcher stats
+    if (mMatcherStats.find(key) != mMatcherStats.end()) {
+        const auto& matcherStats = mMatcherStats[key];
+        for (const auto& stats : matcherStats) {
+            auto output = configStats.add_matcher_stats();
+            output->set_name(stats.first);
+            output->set_matched_times(stats.second);
+            VLOG("matcher %s matched %d times", stats.first.c_str(), stats.second);
+        }
+    }
+    // Add condition stats
+    if (mConditionStats.find(key) != mConditionStats.end()) {
+        const auto& conditionStats = mConditionStats[key];
+        for (const auto& stats : conditionStats) {
+            auto output = configStats.add_condition_stats();
+            output->set_name(stats.first);
+            output->set_max_tuple_counts(stats.second);
+            VLOG("condition %s max output tuple size %d", stats.first.c_str(), stats.second);
+        }
+    }
+    // Add metrics stats
+    if (mMetricsStats.find(key) != mMetricsStats.end()) {
+        const auto& conditionStats = mMetricsStats[key];
+        for (const auto& stats : conditionStats) {
+            auto output = configStats.add_metric_stats();
+            output->set_name(stats.first);
+            output->set_max_tuple_counts(stats.second);
+            VLOG("metrics %s max output tuple size %d", stats.first.c_str(), stats.second);
+        }
+    }
+}
+
+void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
+    lock_guard<std::mutex> lock(mLock);
+
+    if (DEBUG) {
+        time_t t = mStartTimeSec;
+        struct tm* tm = localtime(&t);
+        char timeBuffer[80];
+        strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p", tm);
+        VLOG("=================StatsdStats dump begins====================");
+        VLOG("Stats collection start second: %s", timeBuffer);
+    }
+    ProtoOutputStream proto;
+    proto.write(FIELD_TYPE_INT32 | FIELD_ID_BEGIN_TIME, mStartTimeSec);
+    proto.write(FIELD_TYPE_INT32 | FIELD_ID_END_TIME, (int32_t)time(nullptr));
+
+    VLOG("%lu Config in icebox: ", (unsigned long)mIceBox.size());
+    for (const auto& configStats : mIceBox) {
+        const int numBytes = configStats.ByteSize();
+        vector<char> buffer(numBytes);
+        configStats.SerializeToArray(&buffer[0], numBytes);
+        proto.write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CONFIG_STATS, &buffer[0],
+                    buffer.size());
+
+        // surround the whole block with DEBUG, so that compiler can strip out the code
+        // in production.
+        if (DEBUG) {
+            VLOG("*****ICEBOX*****");
+            VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+                 "#matcher=%d, #alert=%d,  #valid=%d",
+                 configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(),
+                 configStats.deletion_time_sec(), configStats.metric_count(),
+                 configStats.condition_count(), configStats.matcher_count(),
+                 configStats.alert_count(), configStats.is_valid());
+
+            for (const auto& broadcastTime : configStats.broadcast_sent_time_sec()) {
+                VLOG("\tbroadcast time: %d", broadcastTime);
+            }
+
+            for (const auto& dataDropTime : configStats.data_drop_time_sec()) {
+                VLOG("\tdata drop time: %d", dataDropTime);
+            }
+        }
+    }
+
+    for (auto& pair : mConfigStats) {
+        auto& configStats = pair.second;
+        if (DEBUG) {
+            VLOG("********Active Configs***********");
+            VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+                 "#matcher=%d, #alert=%d,  #valid=%d",
+                 configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(),
+                 configStats.deletion_time_sec(), configStats.metric_count(),
+                 configStats.condition_count(), configStats.matcher_count(),
+                 configStats.alert_count(), configStats.is_valid());
+            for (const auto& broadcastTime : configStats.broadcast_sent_time_sec()) {
+                VLOG("\tbroadcast time: %d", broadcastTime);
+            }
+
+            for (const auto& dataDropTime : configStats.data_drop_time_sec()) {
+                VLOG("\tdata drop time: %d", dataDropTime);
+            }
+
+            for (const auto& dumpTime : configStats.dump_report_time_sec()) {
+                VLOG("\tdump report time: %d", dumpTime);
+            }
+        }
+
+        addSubStatsToConfig(pair.first, configStats);
+
+        const int numBytes = configStats.ByteSize();
+        vector<char> buffer(numBytes);
+        configStats.SerializeToArray(&buffer[0], numBytes);
+        proto.write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_CONFIG_STATS, &buffer[0],
+                    buffer.size());
+        // reset the sub stats, the source of truth is in the individual map
+        // they will be repopulated when dumpStats() is called again.
+        configStats.clear_matcher_stats();
+        configStats.clear_condition_stats();
+        configStats.clear_metric_stats();
+    }
+
+    VLOG("********Atom stats***********");
+    const size_t atomCounts = mPushedAtomStats.size();
+    for (size_t i = 2; i < atomCounts; i++) {
+        if (mPushedAtomStats[i] > 0) {
+            long long token =
+                    proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+            proto.end(token);
+
+            VLOG("Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
+        }
+    }
+
+    const int numBytes = mUidMapStats.ByteSize();
+    vector<char> buffer(numBytes);
+    mUidMapStats.SerializeToArray(&buffer[0], numBytes);
+    proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UIDMAP_STATS, &buffer[0], buffer.size());
+    VLOG("UID map stats: bytes=%d, snapshots=%d, changes=%d, snapshots lost=%d, changes "
+         "lost=%d",
+         mUidMapStats.bytes_used(), mUidMapStats.snapshots(), mUidMapStats.changes(),
+         mUidMapStats.dropped_snapshots(), mUidMapStats.dropped_changes());
+
+    output->clear();
+    size_t bufferSize = proto.size();
+    output->resize(bufferSize);
+
+    size_t pos = 0;
+    auto it = proto.data();
+    while (it.readBuffer() != NULL) {
+        size_t toRead = it.currentToRead();
+        std::memcpy(&((*output)[pos]), it.readBuffer(), toRead);
+        pos += toRead;
+        it.rp()->move(toRead);
+    }
+
+    if (reset) {
+        resetInternalLocked();
+    }
+
+    VLOG("reset=%d, returned proto size %lu", reset, (unsigned long)bufferSize);
+    VLOG("=================StatsdStats dump ends====================");
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
new file mode 100644
index 0000000..d6f6566
--- /dev/null
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+#include <gtest/gtest_prod.h>
+#include <mutex>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Keeps track of stats of statsd.
+// Single instance shared across the process. All methods are thread safe.
+class StatsdStats {
+public:
+    static StatsdStats& getInstance();
+    ~StatsdStats(){};
+
+    // TODO: set different limit if the device is low ram.
+    const static int kDimensionKeySizeSoftLimit = 300;
+    const static int kDimensionKeySizeHardLimit = 500;
+
+    const static int kMaxConfigCount = 10;
+    const static int kMaxConditionCountPerConfig = 200;
+    const static int kMaxMetricCountPerConfig = 300;
+    const static int kMaxMatcherCountPerConfig = 500;
+
+    const static int kMaxTimestampCount = 20;
+
+    // Cap the UID map's memory usage to this. This should be fairly high since the UID information
+    // is critical for understanding the metrics.
+    const static size_t kMaxBytesUsedUidMap = 50 * 1024;
+
+    /**
+     * Report a new config has been received and report the static stats about the config.
+     *
+     * The static stats include: the count of metrics, conditions, matchers, and alerts.
+     * If the config is not valid, this config stats will be put into icebox immediately.
+     */
+    void noteConfigReceived(const ConfigKey& key, int metricsCount, int conditionsCount,
+                            int matchersCount, int alertCount, bool isValid);
+    /**
+     * Report a config has been removed.
+     */
+    void noteConfigRemoved(const ConfigKey& key);
+
+    /**
+     * Report a broadcast has been sent to a config owner to collect the data.
+     */
+    void noteBroadcastSent(const ConfigKey& key);
+
+    /**
+     * Report a config's metrics data has been dropped.
+     */
+    void noteDataDropped(const ConfigKey& key);
+
+    /**
+     * Report metrics data report has been sent.
+     *
+     * The report may be requested via StatsManager API, or through adb cmd.
+     */
+    void noteMetricsReportSent(const ConfigKey& key);
+
+    /**
+     * Report the size of output tuple of a condition.
+     *
+     * Note: only report when the condition has an output dimension, and the tuple
+     * count > kDimensionKeySizeSoftLimit.
+     *
+     * [key]: The config key that this condition belongs to.
+     * [name]: The name of the condition.
+     * [size]: The output tuple size.
+     */
+    void noteConditionDimensionSize(const ConfigKey& key, const std::string& name, int size);
+
+    /**
+     * Report the size of output tuple of a metric.
+     *
+     * Note: only report when the metric has an output dimension, and the tuple
+     * count > kDimensionKeySizeSoftLimit.
+     *
+     * [key]: The config key that this metric belongs to.
+     * [name]: The name of the metric.
+     * [size]: The output tuple size.
+     */
+    void noteMetricDimensionSize(const ConfigKey& key, const std::string& name, int size);
+
+    /**
+     * Report a matcher has been matched.
+     *
+     * [key]: The config key that this matcher belongs to.
+     * [name]: The name of the matcher.
+     */
+    void noteMatcherMatched(const ConfigKey& key, const std::string& name);
+
+    /**
+     * Report an atom event has been logged.
+     */
+    void noteAtomLogged(int atomId, int32_t timeSec);
+
+    /**
+     * Records the number of snapshot and delta entries that are being dropped from the uid map.
+     */
+    void noteUidMapDropped(int snapshots, int deltas);
+
+    /**
+     * Updates the number of snapshots currently stored in the uid map.
+     */
+    void setUidMapSnapshots(int snapshots);
+    void setUidMapChanges(int changes);
+    void setCurrentUidMapMemory(int bytes);
+
+    /**
+     * Reset the historical stats. Including all stats in icebox, and the tracked stats about
+     * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
+     * to collect stats after reset() has been called.
+     */
+    void reset();
+
+    /**
+     * Output the stats in protobuf binary format to [buffer].
+     *
+     * [reset]: whether to clear the historical stats after the call.
+     */
+    void dumpStats(std::vector<uint8_t>* buffer, bool reset);
+
+private:
+    StatsdStats();
+
+    mutable std::mutex mLock;
+
+    int32_t mStartTimeSec;
+
+    // Track the number of dropped entries used by the uid map.
+    StatsdStatsReport_UidMapStats mUidMapStats;
+
+    // The stats about the configs that are still in use.
+    std::map<const ConfigKey, StatsdStatsReport_ConfigStats> mConfigStats;
+
+    // Stores the stats for the configs that are no longer in use.
+    std::vector<const StatsdStatsReport_ConfigStats> mIceBox;
+
+    // Stores the number of output tuple of condition trackers when it's bigger than
+    // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
+    // it means some data has been dropped.
+    std::map<const ConfigKey, std::map<const std::string, int>> mConditionStats;
+
+    // Stores the number of output tuple of metric producers when it's bigger than
+    // kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
+    // it means some data has been dropped.
+    std::map<const ConfigKey, std::map<const std::string, int>> mMetricsStats;
+
+    // Stores the number of times a pushed atom is logged.
+    // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms
+    // out of that range will be dropped (it's either pulled atoms or test atoms).
+    // This is a vector, not a map because it will be accessed A LOT -- for each stats log.
+    std::vector<int> mPushedAtomStats;
+
+    // Stores how many times a matcher have been matched.
+    std::map<const ConfigKey, std::map<const std::string, int>> mMatcherStats;
+
+    void noteConfigRemovedInternalLocked(const ConfigKey& key);
+
+    void resetInternalLocked();
+
+    void addSubStatsToConfig(const ConfigKey& key, StatsdStatsReport_ConfigStats& configStats);
+
+    void noteDataDropped(const ConfigKey& key, int32_t timeSec);
+
+    void noteMetricsReportSent(const ConfigKey& key, int32_t timeSec);
+
+    void noteBroadcastSent(const ConfigKey& key, int32_t timeSec);
+
+    FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
+    FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd);
+    FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
+    FRIEND_TEST(StatsdStatsTest, TestSubStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomLog);
+    FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index ffbf248..fea3e9b 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -69,6 +69,10 @@
         return mTagIds;
     }
 
+    const std::string& getName() const {
+        return mName;
+    }
+
 protected:
     // Name of this matching. We don't really need the name, but it makes log message easy to debug.
     const std::string mName;
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index b2c88a0..ad37b01 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -19,8 +19,6 @@
 
 #include "SimpleLogMatchingTracker.h"
 
-#include <log/logprint.h>
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 149b9c1..0064240 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -18,6 +18,7 @@
 #include "Log.h"
 
 #include "CountMetricProducer.h"
+#include "guardrail/StatsdStats.h"
 #include "stats_util.h"
 
 #include <limits.h>
@@ -61,12 +62,11 @@
 const int FIELD_ID_END_BUCKET_NANOS = 2;
 const int FIELD_ID_COUNT = 3;
 
-// TODO: add back AnomalyTracker.
-
-CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
+CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric,
+                                         const int conditionIndex,
                                          const sp<ConditionWizard>& wizard,
                                          const uint64_t startTimeNs)
-    : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric) {
+    : MetricProducer(key, startTimeNs, conditionIndex, wizard), mMetric(metric) {
     // TODO: evaluate initial conditions. and set mConditionMet.
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
         mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
@@ -180,6 +180,26 @@
     mCondition = conditionMet;
 }
 
+bool CountMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
+    if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
+        return false;
+    }
+    // ===========GuardRail==============
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetric.name(),
+                                                           newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("CountMetric %s dropping data for dimension key %s", mMetric.name().c_str(),
+                  newKey.c_str());
+            return true;
+        }
+    }
+
+    return false;
+}
 void CountMetricProducer::onMatchedLogEventInternal(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const map<string, HashableDimensionKey>& conditionKey, bool condition,
@@ -195,6 +215,11 @@
     auto it = mCurrentSlicedCounter->find(eventKey);
 
     if (it == mCurrentSlicedCounter->end()) {
+        // ===========GuardRail==============
+        if (hitGuardRail(eventKey)) {
+            return;
+        }
+
         // create a counter for the new key
         (*mCurrentSlicedCounter)[eventKey] = 1;
     } else {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 293e5b9..f78a199 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -42,8 +42,9 @@
 class CountMetricProducer : public MetricProducer {
 public:
     // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
-    CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const uint64_t startTimeNs);
+    CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric,
+                        const int conditionIndex, const sp<ConditionWizard>& wizard,
+                        const uint64_t startTimeNs);
 
     virtual ~CountMetricProducer();
 
@@ -84,6 +85,8 @@
 
     static const size_t kBucketSize = sizeof(CountBucket{});
 
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index c8e6cce..a0374c0 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -18,6 +18,7 @@
 
 #include "Log.h"
 #include "DurationMetricProducer.h"
+#include "guardrail/StatsdStats.h"
 #include "stats_util.h"
 
 #include <limits.h>
@@ -60,14 +61,14 @@
 const int FIELD_ID_END_BUCKET_NANOS = 2;
 const int FIELD_ID_DURATION = 3;
 
-DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
+DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const DurationMetric& metric,
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
                                                const bool nesting,
                                                const sp<ConditionWizard>& wizard,
                                                const vector<KeyMatcher>& internalDimension,
                                                const uint64_t startTimeNs)
-    : MetricProducer(startTimeNs, conditionIndex, wizard),
+    : MetricProducer(key, startTimeNs, conditionIndex, wizard),
       mMetric(metric),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
@@ -113,13 +114,13 @@
         const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) {
     switch (mMetric.aggregation_type()) {
         case DurationMetric_AggregationType_SUM:
-            return make_unique<OringDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
-                                                     mNested, mCurrentBucketStartTimeNs,
-                                                     mBucketSizeNs, mAnomalyTrackers, bucket);
+            return make_unique<OringDurationTracker>(
+                    mConfigKey, mMetric.name(), eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers, bucket);
         case DurationMetric_AggregationType_MAX_SPARSE:
-            return make_unique<MaxDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
-                                                   mNested, mCurrentBucketStartTimeNs,
-                                                   mBucketSizeNs, mAnomalyTrackers, bucket);
+            return make_unique<MaxDurationTracker>(
+                    mConfigKey, mMetric.name(), eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers, bucket);
     }
 }
 
@@ -238,6 +239,26 @@
     mCurrentBucketNum += numBucketsForward;
 }
 
+bool DurationMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
+    // the key is not new, we are good.
+    if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) {
+        return false;
+    }
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mCurrentSlicedDuration.size() + 1;
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetric.name(),
+                                                           newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("DurationMetric %s dropping data for dimension key %s", mMetric.name().c_str(),
+                  newKey.c_str());
+            return true;
+        }
+    }
+    return false;
+}
+
 void DurationMetricProducer::onMatchedLogEventInternal(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const map<string, HashableDimensionKey>& conditionKeys, bool condition,
@@ -254,6 +275,9 @@
     HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
 
     if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
+        if (hitGuardRail(eventKey)) {
+            return;
+        }
         mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]);
     }
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index ebd5e8d..5b5373e 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -37,9 +37,9 @@
 
 class DurationMetricProducer : public MetricProducer {
 public:
-    DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
-                           const size_t startIndex, const size_t stopIndex,
-                           const size_t stopAllIndex, const bool nesting,
+    DurationMetricProducer(const ConfigKey& key, const DurationMetric& durationMetric,
+                           const int conditionIndex, const size_t startIndex,
+                           const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
                            const sp<ConditionWizard>& wizard,
                            const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
 
@@ -98,6 +98,7 @@
 
     std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey,
                                                            std::vector<DurationBucket>& bucket);
+    bool hitGuardRail(const HashableDimensionKey& newKey);
 
     static const size_t kBucketSize = sizeof(DurationBucket{});
 };
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 567b4c7..95a18f7 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -51,10 +51,11 @@
 const int FIELD_ID_TIMESTAMP_NANOS = 1;
 const int FIELD_ID_ATOMS = 2;
 
-EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex,
+EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric& metric,
+                                         const int conditionIndex,
                                          const sp<ConditionWizard>& wizard,
                                          const uint64_t startTimeNs)
-    : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric) {
+    : MetricProducer(key, startTimeNs, conditionIndex, wizard), mMetric(metric) {
     if (metric.links().size() > 0) {
         mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
                                metric.links().end());
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 5afcebd..33a9510 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -34,8 +34,9 @@
 class EventMetricProducer : public MetricProducer {
 public:
     // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
-    EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const uint64_t startTimeNs);
+    EventMetricProducer(const ConfigKey& key, const EventMetric& eventMetric,
+                        const int conditionIndex, const sp<ConditionWizard>& wizard,
+                        const uint64_t startTimeNs);
 
     virtual ~EventMetricProducer();
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index be030d8..1791654 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -18,6 +18,7 @@
 #include "Log.h"
 
 #include "GaugeMetricProducer.h"
+#include "guardrail/StatsdStats.h"
 #include "stats_util.h"
 
 #include <cutils/log.h>
@@ -62,10 +63,11 @@
 const int FIELD_ID_END_BUCKET_NANOS = 2;
 const int FIELD_ID_GAUGE = 3;
 
-GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
+GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
+                                         const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId,
                                          const int64_t startTimeNs)
-    : MetricProducer(startTimeNs, conditionIndex, wizard),
+    : MetricProducer(key, startTimeNs, conditionIndex, wizard),
       mMetric(metric),
       mPullTagId(pullTagId) {
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
@@ -225,6 +227,26 @@
     }
 }
 
+bool GaugeMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
+    if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) {
+        return false;
+    }
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mCurrentSlicedBucket->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mCurrentSlicedBucket->size() + 1;
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetric.name(),
+                                                           newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("GaugeMetric %s dropping data for dimension key %s", mMetric.name().c_str(),
+                  newKey.c_str());
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void GaugeMetricProducer::onMatchedLogEventInternal(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const map<string, HashableDimensionKey>& conditionKey, bool condition,
@@ -244,12 +266,15 @@
         flushIfNeeded(eventTimeNs);
     }
 
-    // For gauge metric, we just simply use the first guage in the given bucket.
+    // For gauge metric, we just simply use the first gauge in the given bucket.
     if (!mCurrentSlicedBucket->empty()) {
         return;
     }
     const long gauge = getGauge(event);
     if (gauge >= 0) {
+        if (hitGuardRail(eventKey)) {
+            return;
+        }
         (*mCurrentSlicedBucket)[eventKey] = gauge;
     }
     for (auto& tracker : mAnomalyTrackers) {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 4b7654b4..f344303 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -47,9 +47,9 @@
 public:
     // TODO: Pass in the start time from MetricsManager, it should be consistent
     // for all metrics.
-    GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const int pullTagId,
-                        const int64_t startTimeNs);
+    GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& countMetric,
+                        const int conditionIndex, const sp<ConditionWizard>& wizard,
+                        const int pullTagId, const int64_t startTimeNs);
 
     virtual ~GaugeMetricProducer();
 
@@ -100,6 +100,8 @@
 
     int64_t getGauge(const LogEvent& event);
 
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
     static const size_t kBucketSize = sizeof(GaugeBucket{});
 
     FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index ddccf9a..b22ff6f 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -19,6 +19,7 @@
 
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
+#include "config/ConfigKey.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
 
@@ -35,9 +36,10 @@
 // be a no-op.
 class MetricProducer : public virtual PackageInfoListener {
 public:
-    MetricProducer(const int64_t startTimeNs, const int conditionIndex,
+    MetricProducer(const ConfigKey& key, const int64_t startTimeNs, const int conditionIndex,
                    const sp<ConditionWizard>& wizard)
-        : mStartTimeNs(startTimeNs),
+        : mConfigKey(key),
+          mStartTimeNs(startTimeNs),
           mCurrentBucketStartTimeNs(startTimeNs),
           mCurrentBucketNum(0),
           mCondition(conditionIndex >= 0 ? false : true),
@@ -83,6 +85,8 @@
     }
 
 protected:
+    const ConfigKey mConfigKey;
+
     const uint64_t mStartTimeNs;
 
     uint64_t mCurrentBucketStartTimeNs;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 5916b040..e986c1a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -20,6 +20,7 @@
 #include "CountMetricProducer.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
+#include "guardrail/StatsdStats.h"
 #include "matchers/CombinationLogMatchingTracker.h"
 #include "matchers/SimpleLogMatchingTracker.h"
 #include "metrics_manager_util.h"
@@ -36,10 +37,24 @@
 namespace os {
 namespace statsd {
 
-MetricsManager::MetricsManager(const StatsdConfig& config) {
-    mConfigValid = initStatsdConfig(config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
-                                    mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
-                                    mTrackerToMetricMap, mTrackerToConditionMap);
+MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config) : mConfigKey(key) {
+    mConfigValid =
+            initStatsdConfig(key, config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
+                             mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
+                             mTrackerToMetricMap, mTrackerToConditionMap);
+
+    // TODO: add alert size.
+    // no matter whether this config is valid, log it in the stats.
+    StatsdStats::getInstance().noteConfigReceived(key, mAllMetricProducers.size(),
+                                                  mAllConditionTrackers.size(),
+                                                  mAllLogEntryMatchers.size(), 0, mConfigValid);
+    // Guardrail. Reject the config if it's too big.
+    if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
+        mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
+        mAllLogEntryMatchers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
+        ALOGE("This config is too big! Reject!");
+        mConfigValid = false;
+    }
 }
 
 MetricsManager::~MetricsManager() {
@@ -137,6 +152,8 @@
     // For matched LogEntryMatchers, tell relevant metrics that a matched event has come.
     for (size_t i = 0; i < mAllLogEntryMatchers.size(); i++) {
         if (matcherCache[i] == MatchingState::kMatched) {
+            StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
+                                                          mAllLogEntryMatchers[i]->getName());
             auto pair = mTrackerToMetricMap.find(i);
             if (pair != mTrackerToMetricMap.end()) {
                 auto& metricList = pair->second;
@@ -151,9 +168,9 @@
 }
 
 void MetricsManager::onAnomalyAlarmFired(const uint64_t timestampNs,
-                                         sp<const AnomalyAlarm> anomaly) {
+                         unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet) {
     for (const auto& itr : mAllAnomalyTrackers) {
-        itr->declareAnomaly(timestampNs);
+        itr->informAlarmsFired(timestampNs, anomalySet);
     }
 }
 
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a6054e3..62b4c87 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -19,6 +19,7 @@
 #include "anomaly/AnomalyMonitor.h"
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionTracker.h"
+#include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "logd/LogEvent.h"
 #include "matchers/LogMatchingTracker.h"
@@ -33,7 +34,7 @@
 // A MetricsManager is responsible for managing metrics from one single config source.
 class MetricsManager {
 public:
-    MetricsManager(const StatsdConfig& config);
+    MetricsManager(const ConfigKey& configKey, const StatsdConfig& config);
 
     ~MetricsManager();
 
@@ -45,7 +46,8 @@
     // Called when everything should wrap up. We are about to finish (e.g., new config comes).
     void finish();
 
-    void onAnomalyAlarmFired(const uint64_t timestampNs, sp<const AnomalyAlarm> anomaly);
+    void onAnomalyAlarmFired(const uint64_t timestampNs,
+                         unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet);
 
     void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
 
@@ -57,6 +59,8 @@
     size_t byteSize();
 
 private:
+    const ConfigKey mConfigKey;
+
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index cc02c69..66c8419 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -18,6 +18,7 @@
 #include "Log.h"
 
 #include "ValueMetricProducer.h"
+#include "guardrail/StatsdStats.h"
 
 #include <cutils/log.h>
 #include <limits.h>
@@ -67,11 +68,12 @@
 static const uint64_t kDefaultBucketSizeMillis = 60 * 60 * 1000L;
 
 // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
-ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
+ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric& metric,
+                                         const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId,
                                          const uint64_t startTimeNs,
                                          shared_ptr<StatsPullerManager> statsPullerManager)
-    : MetricProducer(startTimeNs, conditionIndex, wizard),
+    : MetricProducer(key, startTimeNs, conditionIndex, wizard),
       mMetric(metric),
       mStatsPullerManager(statsPullerManager),
       mPullTagId(pullTagId) {
@@ -103,10 +105,11 @@
 }
 
 // for testing
-ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
+ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric& metric,
+                                         const int conditionIndex,
                                          const sp<ConditionWizard>& wizard, const int pullTagId,
                                          const uint64_t startTimeNs)
-    : ValueMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs,
+    : ValueMetricProducer(key, metric, conditionIndex, wizard, pullTagId, startTimeNs,
                           make_shared<StatsPullerManager>()) {
 }
 
@@ -238,6 +241,27 @@
     }
 }
 
+bool ValueMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
+    // ===========GuardRail==============
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) {
+        return false;
+    }
+    if (mCurrentSlicedBucket.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mCurrentSlicedBucket.size() + 1;
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetric.name(),
+                                                           newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("ValueMetric %s dropping data for dimension key %s", mMetric.name().c_str(),
+                  newKey.c_str());
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void ValueMetricProducer::onMatchedLogEventInternal(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const map<string, HashableDimensionKey>& conditionKey, bool condition,
@@ -249,6 +273,9 @@
         return;
     }
 
+    if (hitGuardRail(eventKey)) {
+        return;
+    }
     Interval& interval = mCurrentSlicedBucket[eventKey];
 
     long value = get_value(event);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 24c76f2..a024bd8 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -38,9 +38,9 @@
 
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
-    ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const int pullTagId,
-                        const uint64_t startTimeNs);
+    ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
+                        const int conditionIndex, const sp<ConditionWizard>& wizard,
+                        const int pullTagId, const uint64_t startTimeNs);
 
     virtual ~ValueMetricProducer();
 
@@ -77,9 +77,9 @@
     std::shared_ptr<StatsPullerManager> mStatsPullerManager;
 
     // for testing
-    ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const int pullTagId,
-                        const uint64_t startTimeNs,
+    ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
+                        const int conditionIndex, const sp<ConditionWizard>& wizard,
+                        const int pullTagId, const uint64_t startTimeNs,
                         std::shared_ptr<StatsPullerManager> statsPullerManager);
 
     Mutex mLock;
@@ -104,6 +104,8 @@
 
     long get_value(const LogEvent& event);
 
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
     static const size_t kBucketSize = sizeof(ValueBucket{});
 
     FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 7ce7f02..834f7f5 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -19,6 +19,7 @@
 
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
+#include "config/ConfigKey.h"
 #include "stats_util.h"
 
 namespace android {
@@ -59,11 +60,14 @@
 
 class DurationTracker {
 public:
-    DurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                    int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
-                    uint64_t bucketSizeNs, const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
+    DurationTracker(const ConfigKey& key, const string& name, const HashableDimensionKey& eventKey,
+                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+                    uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                    const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                     std::vector<DurationBucket>& bucket)
-        : mEventKey(eventKey),
+        : mConfigKey(key),
+          mName(name),
+          mEventKey(eventKey),
           mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
           mBucketSizeNs(bucketSizeNs),
@@ -138,6 +142,10 @@
             }
         }
     }
+    // A reference to the DurationMetricProducer's config key.
+    const ConfigKey& mConfigKey;
+
+    const std::string mName;
 
     HashableDimensionKey mEventKey;
 
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index e4b3693..4b346dd 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -18,23 +18,50 @@
 
 #include "Log.h"
 #include "MaxDurationTracker.h"
+#include "guardrail/StatsdStats.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-MaxDurationTracker::MaxDurationTracker(const HashableDimensionKey& eventKey,
+MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const string& name,
+                                       const HashableDimensionKey& eventKey,
                                        sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
                                        const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                                        std::vector<DurationBucket>& bucket)
-    : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
-                      anomalyTrackers, bucket) {
+    : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      bucketSizeNs, anomalyTrackers, bucket) {
+}
+
+bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+    // ===========GuardRail==============
+    if (mInfos.find(newKey) != mInfos.end()) {
+        // if the key existed, we are good!
+        return false;
+    }
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mInfos.size() + 1;
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
+                                                           newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("MaxDurTracker %s dropping data for dimension key %s", mName.c_str(),
+                  newKey.c_str());
+            return true;
+        }
+    }
+    return false;
 }
 
 void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
                                    const uint64_t eventTime, const ConditionKey& conditionKey) {
     // this will construct a new DurationInfo if this key didn't exist.
+    if (hitGuardRail(key)) {
+        return;
+    }
+
     DurationInfo& duration = mInfos[key];
     duration.conditionKeys = conditionKey;
     VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 0f79ffe..e0d1466 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,7 +28,8 @@
 // they stop or bucket expires.
 class MaxDurationTracker : public DurationTracker {
 public:
-    MaxDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+    MaxDurationTracker(const ConfigKey& key, const string& name,
+                       const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
                        int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
                        uint64_t bucketSizeNs,
                        const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
@@ -53,6 +54,9 @@
     void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
                               const uint64_t timestamp);
 
+    // return true if we should not allow newKey to be tracked because we are above the threshold
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
     FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration);
     FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
     FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 76f8514..abdfbc0 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -16,6 +16,7 @@
 #define DEBUG true
 #include "Log.h"
 #include "OringDurationTracker.h"
+#include "guardrail/StatsdStats.h"
 
 namespace android {
 namespace os {
@@ -23,21 +24,45 @@
 
 using std::pair;
 
-OringDurationTracker::OringDurationTracker(const HashableDimensionKey& eventKey,
+OringDurationTracker::OringDurationTracker(const ConfigKey& key, const string& name,
+                                           const HashableDimensionKey& eventKey,
                                            sp<ConditionWizard> wizard, int conditionIndex,
                                            bool nesting, uint64_t currentBucketStartNs,
                                            uint64_t bucketSizeNs,
                                            const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                                            std::vector<DurationBucket>& bucket)
-    : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
-                      anomalyTrackers, bucket),
+    : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      bucketSizeNs, anomalyTrackers, bucket),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
 }
 
+bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+    // ===========GuardRail==============
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mConditionKeyMap.find(newKey) != mConditionKeyMap.end()) {
+        return false;
+    }
+    if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mConditionKeyMap.size() + 1;
+        StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
+                                                           newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("OringDurTracker %s dropping data for dimension key %s", mName.c_str(),
+                  newKey.c_str());
+            return true;
+        }
+    }
+    return false;
+}
+
 void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
                                      const uint64_t eventTime, const ConditionKey& conditionKey) {
+    if (hitGuardRail(key)) {
+        return;
+    }
     if (condition) {
         if (mStarted.size() == 0) {
             mLastStartTime = eventTime;
@@ -268,12 +293,12 @@
     pastNs += currRemainingBucketSizeNs;
 
     // Now deal with the past buckets, starting with the oldest.
-    for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastPackets();
+    for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastBuckets();
          futBucketIdx++) {
         // We now overwrite the oldest bucket with the previous 'current', and start a new
         // 'current'.
         pastNs -= anomalyTracker.getPastBucketValue(
-                mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastPackets() + futBucketIdx);
+                mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futBucketIdx);
         leftNs = thresholdNs - pastNs;
         if (leftNs <= mBucketSizeNs) {  // Predict anomaly will occur in this bucket.
             return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) +
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index ef32fdb..a8404a9 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,7 +27,8 @@
 // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
 class OringDurationTracker : public DurationTracker {
 public:
-    OringDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+    OringDurationTracker(const ConfigKey& key, const string& name,
+                         const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
                          int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
                          uint64_t bucketSizeNs,
                          const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
@@ -58,6 +59,9 @@
     int64_t mLastStartTime;
     std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
 
+    // return true if we should not allow newKey to be tracked because we are above the threshold
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
     FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap);
     FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
     FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 4660263..9e5163f 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
 #include "../condition/CombinationConditionTracker.h"
 #include "../condition/SimpleConditionTracker.h"
 #include "../external/StatsPullerManager.h"
@@ -134,7 +137,8 @@
     return true;
 }
 
-bool initConditions(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
+bool initConditions(const ConfigKey& key, const StatsdConfig& config,
+                    const unordered_map<string, int>& logTrackerMap,
                     unordered_map<string, int>& conditionTrackerMap,
                     vector<sp<ConditionTracker>>& allConditionTrackers,
                     unordered_map<int, std::vector<int>>& trackerToConditionMap) {
@@ -149,7 +153,7 @@
         switch (condition.contents_case()) {
             case Condition::ContentsCase::kSimpleCondition: {
                 allConditionTrackers.push_back(new SimpleConditionTracker(
-                        condition.name(), index, condition.simple_condition(), logTrackerMap));
+                        key, condition.name(), index, condition.simple_condition(), logTrackerMap));
                 break;
             }
             case Condition::ContentsCase::kCombination: {
@@ -184,7 +188,8 @@
     return true;
 }
 
-bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
+bool initMetrics(const ConfigKey& key, const StatsdConfig& config,
+                 const unordered_map<string, int>& logTrackerMap,
                  const unordered_map<string, int>& conditionTrackerMap,
                  const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -219,9 +224,12 @@
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
-            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
-                                       metric.links(), allConditionTrackers, conditionIndex,
-                                       conditionToMetricMap);
+            bool good = handleMetricWithConditions(
+                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
+                    allConditionTrackers, conditionIndex, conditionToMetricMap);
+            if (!good) {
+                return false;
+            }
         } else {
             if (metric.links_size() > 0) {
                 ALOGW("metrics has a EventConditionLink but doesn't have a condition");
@@ -230,7 +238,7 @@
         }
 
         sp<MetricProducer> countProducer =
-                new CountMetricProducer(metric, conditionIndex, wizard, startTimeNs);
+                new CountMetricProducer(key, metric, conditionIndex, wizard, startTimeNs);
         allMetricProducers.push_back(countProducer);
     }
 
@@ -287,9 +295,12 @@
         int conditionIndex = -1;
 
         if (metric.has_condition()) {
-            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
-                                       metric.links(), allConditionTrackers, conditionIndex,
-                                       conditionToMetricMap);
+            bool good = handleMetricWithConditions(
+                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
+                    allConditionTrackers, conditionIndex, conditionToMetricMap);
+            if (!good) {
+                return false;
+            }
         } else {
             if (metric.links_size() > 0) {
                 ALOGW("metrics has a EventConditionLink but doesn't have a condition");
@@ -298,8 +309,8 @@
         }
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
-                metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
-                nesting, wizard, internalDimension, startTimeNs);
+                key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
+                trackerIndices[2], nesting, wizard, internalDimension, startTimeNs);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -321,9 +332,12 @@
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
-            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
-                                       metric.links(), allConditionTrackers, conditionIndex,
-                                       conditionToMetricMap);
+            bool good = handleMetricWithConditions(
+                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
+                    allConditionTrackers, conditionIndex, conditionToMetricMap);
+            if (!good) {
+                return false;
+            }
         } else {
             if (metric.links_size() > 0) {
                 ALOGW("metrics has a EventConditionLink but doesn't have a condition");
@@ -332,7 +346,7 @@
         }
 
         sp<MetricProducer> eventMetric =
-                new EventMetricProducer(metric, conditionIndex, wizard, startTimeNs);
+                new EventMetricProducer(key, metric, conditionIndex, wizard, startTimeNs);
 
         allMetricProducers.push_back(eventMetric);
     }
@@ -368,9 +382,12 @@
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
-            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
-                                       metric.links(), allConditionTrackers, conditionIndex,
-                                       conditionToMetricMap);
+            bool good = handleMetricWithConditions(
+                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
+                    allConditionTrackers, conditionIndex, conditionToMetricMap);
+            if (!good) {
+                return false;
+            }
         } else {
             if (metric.links_size() > 0) {
                 ALOGW("metrics has a EventConditionLink but doesn't have a condition");
@@ -378,8 +395,8 @@
             }
         }
 
-        sp<MetricProducer> valueProducer =
-                new ValueMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
+        sp<MetricProducer> valueProducer = new ValueMetricProducer(key, metric, conditionIndex,
+                                                                   wizard, pullTagId, startTimeNs);
         allMetricProducers.push_back(valueProducer);
     }
 
@@ -414,9 +431,12 @@
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
-            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
-                                       metric.links(), allConditionTrackers, conditionIndex,
-                                       conditionToMetricMap);
+            bool good = handleMetricWithConditions(
+                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
+                    allConditionTrackers, conditionIndex, conditionToMetricMap);
+            if (!good) {
+                return false;
+            }
         } else {
             if (metric.links_size() > 0) {
                 ALOGW("metrics has a EventConditionLink but doesn't have a condition");
@@ -424,8 +444,8 @@
             }
         }
 
-        sp<MetricProducer> gaugeProducer =
-                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
+        sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(key, metric, conditionIndex,
+                                                                   wizard, pullTagId, startTimeNs);
         allMetricProducers.push_back(gaugeProducer);
     }
     return true;
@@ -442,16 +462,31 @@
                   alert.metric_name().c_str());
             return false;
         }
+        if (alert.trigger_if_sum_gt() < 0 || alert.number_of_buckets() <= 0) {
+            ALOGW("invalid alert: threshold=%lld num_buckets= %d",
+                  alert.trigger_if_sum_gt(), alert.number_of_buckets());
+            return false;
+        }
         const int metricIndex = itr->second;
-        sp<AnomalyTracker> anomalyTracker =
-                new AnomalyTracker(alert, allMetricProducers[metricIndex]->getBuckeSizeInNs());
+        if (alert.trigger_if_sum_gt() >
+                  (int64_t) alert.number_of_buckets()
+                  * allMetricProducers[metricIndex]->getBuckeSizeInNs()) {
+            ALOGW("invalid alert: threshold (%lld) > possible recordable value (%d x %lld)",
+                  alert.trigger_if_sum_gt(), alert.number_of_buckets(),
+                  (long long) allMetricProducers[metricIndex]->getBuckeSizeInNs());
+            return false;
+        }
+
+        // TODO: Give each MetricProducer a method called createAnomalyTracker(alert), which
+        //       creates either an AnomalyTracker or a DurationAnomalyTracker and returns it.
+        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
         allMetricProducers[metricIndex]->addAnomalyTracker(anomalyTracker);
         allAnomalyTrackers.push_back(anomalyTracker);
     }
     return true;
 }
 
-bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, set<int>& allTagIds,
                       vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
@@ -469,13 +504,13 @@
     }
     ALOGD("initLogMatchingTrackers succeed...");
 
-    if (!initConditions(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
+    if (!initConditions(key, config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
                         trackerToConditionMap)) {
         ALOGE("initConditionTrackers failed");
         return false;
     }
 
-    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
+    if (!initMetrics(key, config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
                      allConditionTrackers, allMetricProducers, conditionToMetricMap,
                      trackerToMetricMap, metricProducerMap)) {
         ALOGE("initMetricProducers failed");
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 7d7e0c3..e7cbd53 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -36,6 +36,7 @@
 
 // Initialize the LogMatchingTrackers.
 // input:
+// [key]: the config key that this config belongs to
 // [config]: the input StatsdConfig
 // output:
 // [logTrackerMap]: this map should contain matcher name to index mapping
@@ -48,6 +49,7 @@
 
 // Initialize ConditionTrackers
 // input:
+// [key]: the config key that this config belongs to
 // [config]: the input config
 // [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
 // output:
@@ -55,7 +57,7 @@
 // [allConditionTrackers]: stores the sp to all the ConditionTrackers
 // [trackerToConditionMap]: contain the mapping from index of
 //                        log tracker to condition trackers that use the log tracker
-bool initConditions(const StatsdConfig& config,
+bool initConditions(const ConfigKey& key, const StatsdConfig& config,
                     const std::unordered_map<std::string, int>& logTrackerMap,
                     std::unordered_map<std::string, int>& conditionTrackerMap,
                     std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -64,6 +66,7 @@
 
 // Initialize MetricProducers.
 // input:
+// [key]: the config key that this config belongs to
 // [config]: the input config
 // [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
 // [conditionTrackerMap]: condition name to index mapping
@@ -73,7 +76,8 @@
 //                          the list of MetricProducer index
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 bool initMetrics(
-        const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
+        const ConfigKey& key, const StatsdConfig& config,
+        const std::unordered_map<std::string, int>& logTrackerMap,
         const std::unordered_map<std::string, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
         const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
@@ -84,7 +88,7 @@
 
 // Initialize MetricsManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
-bool initStatsdConfig(const StatsdConfig& config, std::set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, std::set<int>& allTagIds,
                       std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 6c32d3e..db592e2 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -13,11 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+#define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
+#include "guardrail/StatsdStats.h"
 #include "packages/UidMap.h"
 
+#include <android/os/IStatsCompanionService.h>
+#include <binder/IServiceManager.h>
 #include <utils/Errors.h>
 
 using namespace android;
@@ -26,6 +29,11 @@
 namespace os {
 namespace statsd {
 
+UidMap::UidMap() : mBytesUsed(0) {
+}
+UidMap::~UidMap() {
+}
+
 bool UidMap::hasApp(int uid, const string& packageName) const {
     lock_guard<mutex> lock(mMutex);
 
@@ -73,6 +81,10 @@
         t->set_version(int(versionCode[j]));
         t->set_uid(uid[j]);
     }
+    mBytesUsed += snapshot->ByteSize();
+    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+    StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
+    ensureBytesUsedBelowLimit();
 }
 
 void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
@@ -96,6 +108,10 @@
     log->set_app(app);
     log->set_uid(uid);
     log->set_version(versionCode);
+    mBytesUsed += log->ByteSize();
+    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+    StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+    ensureBytesUsedBelowLimit();
 
     auto range = mMap.equal_range(int(uid));
     for (auto it = range.first; it != range.second; ++it) {
@@ -103,7 +119,7 @@
             it->second.versionCode = int(versionCode);
             return;
         }
-        ALOGD("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
+        VLOG("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
         return;
     }
 
@@ -111,6 +127,28 @@
     mMap.insert(make_pair(uid, AppData(app, int(versionCode))));
 }
 
+void UidMap::ensureBytesUsedBelowLimit() {
+    size_t limit;
+    if (maxBytesOverride <= 0) {
+        limit = StatsdStats::kMaxBytesUsedUidMap;
+    } else {
+        limit = maxBytesOverride;
+    }
+    while (mBytesUsed > limit) {
+        VLOG("Bytes used %zu is above limit %zu, need to delete something", mBytesUsed, limit);
+        if (mOutput.snapshots_size() > 0) {
+            auto snapshots = mOutput.mutable_snapshots();
+            snapshots->erase(snapshots->begin());  // Remove first snapshot.
+            StatsdStats::getInstance().noteUidMapDropped(1, 0);
+        } else if (mOutput.changes_size() > 0) {
+            auto changes = mOutput.mutable_changes();
+            changes->DeleteSubrange(0, 1);
+            StatsdStats::getInstance().noteUidMapDropped(0, 1);
+        }
+        mBytesUsed = mOutput.ByteSize();
+    }
+}
+
 void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
     removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
 }
@@ -128,6 +166,10 @@
     log->set_timestamp_nanos(timestamp);
     log->set_app(app);
     log->set_uid(uid);
+    mBytesUsed += log->ByteSize();
+    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+    StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+    ensureBytesUsedBelowLimit();
 
     auto range = mMap.equal_range(int(uid));
     for (auto it = range.first; it != range.second; ++it) {
@@ -136,7 +178,7 @@
             return;
         }
     }
-    ALOGD("removeApp failed to find the app %s with uid %i to remove", app.c_str(), uid);
+    VLOG("removeApp failed to find the app %s with uid %i to remove", app.c_str(), uid);
     return;
 }
 
@@ -177,7 +219,6 @@
 
 void UidMap::clearOutput() {
     mOutput.Clear();
-
     // Re-initialize the initial state for the outputs. This results in extra data being uploaded
     // but helps ensure we can re-construct the UID->app name, versionCode mapping in server.
     auto snapshot = mOutput.add_snapshots();
@@ -187,6 +228,12 @@
         t->set_version(it.second.versionCode);
         t->set_uid(it.first);
     }
+
+    // Also update the guardrail trackers.
+    StatsdStats::getInstance().setUidMapChanges(0);
+    StatsdStats::getInstance().setUidMapSnapshots(1);
+    mBytesUsed = snapshot->ByteSize();
+    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
 }
 
 int64_t UidMap::getMinimumTimestampNs() {
@@ -201,6 +248,10 @@
     return m;
 }
 
+size_t UidMap::getBytesUsed() {
+    return mBytesUsed;
+}
+
 UidMapping UidMap::getOutput(const ConfigKey& key) {
     return getOutput(time(nullptr) * NS_PER_SEC, key);
 }
@@ -236,6 +287,10 @@
             }
         }
     }
+    mBytesUsed = mOutput.ByteSize();  // Compute actual size after potential deletions.
+    StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
+    StatsdStats::getInstance().setUidMapChanges(mOutput.changes_size());
+    StatsdStats::getInstance().setUidMapSnapshots(mOutput.snapshots_size());
     return ret;
 }
 
@@ -250,6 +305,23 @@
 
 void UidMap::OnConfigUpdated(const ConfigKey& key) {
     mLastUpdatePerConfigKey[key] = -1;
+
+    // Ensure there is at least one snapshot available since this configuration also needs to know
+    // what all the uid's represent.
+    if (mOutput.snapshots_size() == 0) {
+        sp<IStatsCompanionService> statsCompanion = nullptr;
+        // Get statscompanion service from service manager
+        const sp<IServiceManager> sm(defaultServiceManager());
+        if (sm != nullptr) {
+            const String16 name("statscompanion");
+            statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
+            if (statsCompanion == nullptr) {
+                ALOGW("statscompanion service unavailable!");
+                return;
+            }
+            statsCompanion->triggerUidSnapshot();
+        }
+    }
 }
 
 void UidMap::OnConfigRemoved(const ConfigKey& key) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 24eb966..d2971c9 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -50,15 +50,16 @@
 // at any given moment. This map must be updated by StatsCompanionService.
 class UidMap : public virtual android::RefBase {
 public:
+    UidMap();
+    ~UidMap();
+
     /*
      * All three inputs must be the same size, and the jth element in each array refers to the same
      * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
      */
-    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
     void updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
                    const vector<String16>& packageName);
 
-    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
     void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
     void removeApp(const String16& packageName, const int32_t& uid);
 
@@ -98,11 +99,13 @@
     // in case we lose a previous upload.
     void clearOutput();
 
+    // Get currently cached value of memory used by UID map.
+    size_t getBytesUsed();
+
 private:
     void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
                    const vector<int32_t>& versionCode, const vector<String16>& packageName);
 
-    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
     void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid,
                    const int32_t& versionCode);
     void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid);
@@ -135,8 +138,22 @@
     // Returns the minimum value from mConfigKeys.
     int64_t getMinimumTimestampNs();
 
+    // If our current used bytes is above the limit, then we clear out the earliest snapshot. If
+    // there are no more snapshots, then we clear out the earliest delta. We repeat the deletions
+    // until the memory consumed by mOutput is below the specified limit.
+    void ensureBytesUsedBelowLimit();
+
+    // Override used for testing the max memory allowed by uid map. -1 means we use the value
+    // specified in StatsdStats.h with the rest of the guardrails.
+    size_t maxBytesOverride = -1;
+
+    // Cache the size of mOutput;
+    size_t mBytesUsed;
+
     // Allows unit-test to access private methods.
     FRIEND_TEST(UidMapTest, TestClearingOutput);
+    FRIEND_TEST(UidMapTest, TestMemoryComputed);
+    FRIEND_TEST(UidMapTest, TestMemoryGuardrail);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4783cd89..cc8a26d 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -172,3 +172,61 @@
 
   repeated ConfigMetricsReport reports = 2;
 }
+
+message StatsdStatsReport {
+    optional int32 stats_begin_time_sec = 1;
+
+    optional int32 stats_end_time_sec = 2;
+
+    message MatcherStats {
+        optional string name = 1;
+        optional int32 matched_times = 2;
+    }
+
+    message ConditionStats {
+        optional string name = 1;
+        optional int32 max_tuple_counts = 2;
+    }
+
+    message MetricStats {
+        optional string name = 1;
+        optional int32 max_tuple_counts = 2;
+    }
+
+    message ConfigStats {
+        optional int32 uid = 1;
+        optional string name = 2;
+        optional int32 creation_time_sec = 3;
+        optional int32 deletion_time_sec = 4;
+        optional int32 metric_count = 5;
+        optional int32 condition_count = 6;
+        optional int32 matcher_count = 7;
+        optional int32 alert_count = 8;
+        optional bool is_valid = 9;
+
+        repeated int32 broadcast_sent_time_sec = 10;
+        repeated int32 data_drop_time_sec = 11;
+        repeated int32 dump_report_time_sec = 12;
+        repeated MatcherStats matcher_stats = 13;
+        repeated ConditionStats condition_stats = 14;
+        repeated MetricStats metric_stats = 15;
+    }
+
+    repeated ConfigStats config_stats = 3;
+
+    message AtomStats {
+        optional int32 tag = 1;
+        optional int32 count = 2;
+    }
+
+    repeated AtomStats atom_stats = 7;
+
+    message UidMapStats {
+        optional int32 snapshots = 1;
+        optional int32 changes = 2;
+        optional int32 bytes_used = 3;
+        optional int32 dropped_snapshots = 4;
+        optional int32 dropped_changes = 5;
+    }
+    optional UidMapStats uidmap_stats = 8;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index a95e899..62f06a7 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -40,7 +40,7 @@
 
 void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
     int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
-    if (fd != -1) {
+    if (fd == -1) {
         VLOG("Attempt to access %s but failed", file);
         return;
     }
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 618aef6..696fddf 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -62,7 +62,8 @@
 }
 
 TEST(ConfigManagerTest, TestFakeConfig) {
-    auto metricsManager = std::make_unique<MetricsManager>(build_fake_config());
+    auto metricsManager =
+            std::make_unique<MetricsManager>(ConfigKey(0, "test"), build_fake_config());
     EXPECT_TRUE(metricsManager->isConfigValid());
 }
 
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 3dd4e70..5384e0c 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -40,6 +40,8 @@
 
 // TODO: ADD MORE TEST CASES.
 
+const ConfigKey kConfigKey(0, "test");
+
 StatsdConfig buildGoodConfig() {
     StatsdConfig config;
     config.set_name("12345");
@@ -163,6 +165,25 @@
     return config;
 }
 
+StatsdConfig buildMissingCondition() {
+    StatsdConfig config;
+    config.set_name("12345");
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_name("3");
+    metric->set_what("SCREEN_EVENT");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    metric->set_condition("SOME_CONDITION");
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_EVENT");
+
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2);
+
+    return config;
+}
+
 StatsdConfig buildDimensionMetricsWithMultiTags() {
     StatsdConfig config;
     config.set_name("12345");
@@ -254,9 +275,9 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_TRUE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                 allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
-                                 trackerToMetricMap, trackerToConditionMap));
+    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                 allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                 conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
     EXPECT_EQ(1u, allMetricProducers.size());
     EXPECT_EQ(1u, allAnomalyTrackers.size());
 }
@@ -272,9 +293,9 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap));
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
@@ -288,9 +309,9 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap));
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestMissingMatchers) {
@@ -303,9 +324,24 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap));
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, TestMissingCondition) {
+    StatsdConfig config = buildMissingCondition();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestCircleConditionDependency) {
@@ -319,9 +355,9 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap));
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
@@ -335,9 +371,9 @@
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
-    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap));
+    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+                                  allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+                                  conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
 }
 
 #else
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 0c19468..aa194e6 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -15,6 +15,7 @@
 #include "packages/UidMap.h"
 #include "StatsLogProcessor.h"
 #include "config/ConfigKey.h"
+#include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "statslog.h"
 
@@ -35,7 +36,8 @@
 TEST(UidMapTest, TestIsolatedUID) {
     sp<UidMap> m = new UidMap();
     sp<AnomalyMonitor> anomalyMonitor;
-    StatsLogProcessor p(m, anomalyMonitor, nullptr);
+    // Construct the processor with a dummy sendBroadcast function that does nothing.
+    StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {});
     LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
     addEvent.write(100);  // parent UID
     addEvent.write(101);  // isolated UID
@@ -114,26 +116,34 @@
     versions.push_back(4);
     versions.push_back(5);
     m.updateMap(1, uids, versions, apps);
+    EXPECT_EQ(1, m.mOutput.snapshots_size());
 
     UidMapping results = m.getOutput(2, config1);
     EXPECT_EQ(1, results.snapshots_size());
 
     // It should be cleared now
+    EXPECT_EQ(0, m.mOutput.snapshots_size());
     results = m.getOutput(3, config1);
     EXPECT_EQ(0, results.snapshots_size());
 
     // Now add another configuration.
     m.OnConfigUpdated(config2);
     m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
+    EXPECT_EQ(1, m.mOutput.changes_size());
     results = m.getOutput(6, config1);
     EXPECT_EQ(0, results.snapshots_size());
     EXPECT_EQ(1, results.changes_size());
+    EXPECT_EQ(1, m.mOutput.changes_size());
 
-    // Now we still haven't been able to delete anything
+    // Add another delta update.
     m.updateApp(7, String16(kApp2.c_str()), 1001, 41);
+    EXPECT_EQ(2, m.mOutput.changes_size());
+
+    // We still can't remove anything.
     results = m.getOutput(8, config1);
     EXPECT_EQ(0, results.snapshots_size());
     EXPECT_EQ(2, results.changes_size());
+    EXPECT_EQ(2, m.mOutput.changes_size());
 
     results = m.getOutput(9, config2);
     EXPECT_EQ(0, results.snapshots_size());
@@ -142,6 +152,66 @@
     EXPECT_EQ(0, m.mOutput.snapshots_size());
     EXPECT_EQ(0, m.mOutput.changes_size());
 }
+
+TEST(UidMapTest, TestMemoryComputed) {
+    UidMap m;
+
+    ConfigKey config1(1, "config1");
+    m.OnConfigUpdated(config1);
+
+    size_t startBytes = m.mBytesUsed;
+    vector<int32_t> uids;
+    vector<int32_t> versions;
+    vector<String16> apps;
+    uids.push_back(1000);
+    apps.push_back(String16(kApp1.c_str()));
+    versions.push_back(1);
+    m.updateMap(1, uids, versions, apps);
+    size_t snapshot_bytes = m.mBytesUsed;
+    EXPECT_TRUE(snapshot_bytes > startBytes);
+
+    m.updateApp(3, String16(kApp1.c_str()), 1000, 40);
+    EXPECT_TRUE(m.mBytesUsed > snapshot_bytes);
+    size_t bytesWithSnapshotChange = m.mBytesUsed;
+
+    m.getOutput(2, config1);
+    EXPECT_TRUE(m.mBytesUsed < bytesWithSnapshotChange);
+    size_t prevBytes = m.mBytesUsed;
+
+    m.getOutput(4, config1);
+    EXPECT_TRUE(m.mBytesUsed < prevBytes);
+}
+
+TEST(UidMapTest, TestMemoryGuardrail) {
+    UidMap m;
+    string buf;
+
+    ConfigKey config1(1, "config1");
+    m.OnConfigUpdated(config1);
+
+    size_t startBytes = m.mBytesUsed;
+    vector<int32_t> uids;
+    vector<int32_t> versions;
+    vector<String16> apps;
+    for (int i = 0; i < 100; i++) {
+        uids.push_back(1);
+        buf = "EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY." + to_string(i);
+        apps.push_back(String16(buf.c_str()));
+        versions.push_back(1);
+    }
+    m.updateMap(1, uids, versions, apps);
+    EXPECT_EQ(1, m.mOutput.snapshots_size());
+
+    m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2);
+    EXPECT_EQ(1, m.mOutput.snapshots_size());
+    EXPECT_EQ(1, m.mOutput.changes_size());
+
+    // Now force deletion by limiting the memory to hold one delta change.
+    m.maxBytesOverride = 80; // Since the app string alone requires >45 characters.
+    m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4);
+    EXPECT_EQ(0, m.mOutput.snapshots_size());
+    EXPECT_EQ(1, m.mOutput.changes_size());
+}
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index e0200f27..65c2a05 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -51,7 +51,7 @@
     alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    AnomalyTracker anomalyTracker(alert, bucketSizeNs);
+    AnomalyTracker anomalyTracker(alert);
 
     std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
     int64_t eventTimestamp0 = 10;
@@ -168,7 +168,7 @@
     alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    AnomalyTracker anomalyTracker(alert, bucketSizeNs);
+    AnomalyTracker anomalyTracker(alert);
 
     std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
     std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 11fb011..8b0b6a4 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -28,6 +28,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 SimpleCondition getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
                                          bool outputSlicedUid) {
     SimpleCondition simpleCondition;
@@ -76,8 +78,8 @@
     trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
     trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
 
-    SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*tracker index*/, simpleCondition,
-                                            trackerNameIndexMap);
+    SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", 0 /*tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
 
@@ -158,8 +160,9 @@
     trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
     trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
 
-    SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*condition tracker index*/,
-                                            simpleCondition, trackerNameIndexMap);
+    SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON",
+                                            0 /*condition tracker index*/, simpleCondition,
+                                            trackerNameIndexMap);
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
 
@@ -227,8 +230,9 @@
     trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
     trackerNameIndexMap["RELEASE_ALL"] = 2;
 
-    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
-                                            simpleCondition, trackerNameIndexMap);
+    SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
+                                            0 /*condition tracker index*/, simpleCondition,
+                                            trackerNameIndexMap);
     int uid = 111;
 
     LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -238,6 +242,7 @@
     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);
@@ -308,8 +313,9 @@
     trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
     trackerNameIndexMap["RELEASE_ALL"] = 2;
 
-    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
-                                            simpleCondition, trackerNameIndexMap);
+    SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
+                                            0 /*condition tracker index*/, simpleCondition,
+                                            trackerNameIndexMap);
     int uid1 = 111;
     string uid1_wl1 = "wl1_1";
     int uid2 = 222;
@@ -322,6 +328,7 @@
     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);
@@ -392,8 +399,9 @@
     trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
     trackerNameIndexMap["RELEASE_ALL"] = 2;
 
-    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
-                                            simpleCondition, trackerNameIndexMap);
+    SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
+                                            0 /*condition tracker index*/, simpleCondition,
+                                            trackerNameIndexMap);
     int uid1 = 111;
     int uid2 = 222;
 
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
new file mode 100644
index 0000000..a8193dd
--- /dev/null
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -0,0 +1,274 @@
+// 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/guardrail/StatsdStats.h"
+#include "statslog.h"
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+TEST(StatsdStatsTest, TestValidConfigAdd) {
+    StatsdStats stats;
+    string name = "StatsdTest";
+    ConfigKey key(0, name);
+    const int metricsCount = 10;
+    const int conditionsCount = 20;
+    const int matchersCount = 30;
+    const int alertsCount = 10;
+    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount,
+                             true /*valid config*/);
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false /*reset stats*/);
+
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+    EXPECT_EQ(1, report.config_stats_size());
+    const auto& configReport = report.config_stats(0);
+    EXPECT_EQ(0, configReport.uid());
+    EXPECT_EQ(name, configReport.name());
+    EXPECT_EQ(metricsCount, configReport.metric_count());
+    EXPECT_EQ(conditionsCount, configReport.condition_count());
+    EXPECT_EQ(matchersCount, configReport.matcher_count());
+    EXPECT_EQ(alertsCount, configReport.alert_count());
+    EXPECT_EQ(true, configReport.is_valid());
+    EXPECT_FALSE(configReport.has_deletion_time_sec());
+}
+
+TEST(StatsdStatsTest, TestInvalidConfigAdd) {
+    StatsdStats stats;
+    string name = "StatsdTest";
+    ConfigKey key(0, name);
+    const int metricsCount = 10;
+    const int conditionsCount = 20;
+    const int matchersCount = 30;
+    const int alertsCount = 10;
+    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount,
+                             false /*bad config*/);
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+    EXPECT_EQ(1, report.config_stats_size());
+    const auto& configReport = report.config_stats(0);
+    // The invalid config should be put into icebox with a deletion time.
+    EXPECT_TRUE(configReport.has_deletion_time_sec());
+}
+
+TEST(StatsdStatsTest, TestConfigRemove) {
+    StatsdStats stats;
+    string name = "StatsdTest";
+    ConfigKey key(0, name);
+    const int metricsCount = 10;
+    const int conditionsCount = 20;
+    const int matchersCount = 30;
+    const int alertsCount = 10;
+    stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, true);
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+    EXPECT_EQ(1, report.config_stats_size());
+    const auto& configReport = report.config_stats(0);
+    EXPECT_FALSE(configReport.has_deletion_time_sec());
+
+    stats.noteConfigRemoved(key);
+    stats.dumpStats(&output, false);
+    good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+    EXPECT_EQ(1, report.config_stats_size());
+    const auto& configReport2 = report.config_stats(0);
+    EXPECT_TRUE(configReport2.has_deletion_time_sec());
+}
+
+TEST(StatsdStatsTest, TestSubStats) {
+    StatsdStats stats;
+    ConfigKey key(0, "test");
+    stats.noteConfigReceived(key, 2, 3, 4, 5, true);
+
+    stats.noteMatcherMatched(key, "matcher1");
+    stats.noteMatcherMatched(key, "matcher1");
+    stats.noteMatcherMatched(key, "matcher2");
+
+    stats.noteConditionDimensionSize(key, "condition1", 250);
+    stats.noteConditionDimensionSize(key, "condition1", 240);
+
+    stats.noteMetricDimensionSize(key, "metric1", 201);
+    stats.noteMetricDimensionSize(key, "metric1", 202);
+
+    // broadcast-> 2
+    stats.noteBroadcastSent(key);
+    stats.noteBroadcastSent(key);
+
+    // data drop -> 1
+    stats.noteDataDropped(key);
+
+    // dump report -> 3
+    stats.noteMetricsReportSent(key);
+    stats.noteMetricsReportSent(key);
+    stats.noteMetricsReportSent(key);
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, true);  // Dump and reset stats
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+    EXPECT_EQ(1, report.config_stats_size());
+    const auto& configReport = report.config_stats(0);
+    EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size());
+    EXPECT_EQ(1, configReport.data_drop_time_sec_size());
+    EXPECT_EQ(3, configReport.dump_report_time_sec_size());
+
+    EXPECT_EQ(2, configReport.matcher_stats_size());
+
+    // matcher1 is the first in the list
+    if (!configReport.matcher_stats(0).name().compare("matcher1")) {
+        EXPECT_EQ(2, configReport.matcher_stats(0).matched_times());
+        EXPECT_EQ(1, configReport.matcher_stats(1).matched_times());
+        EXPECT_EQ("matcher2", configReport.matcher_stats(1).name());
+    } else {
+        // matcher1 is the second in the list.
+        EXPECT_EQ(1, configReport.matcher_stats(0).matched_times());
+        EXPECT_EQ("matcher2", configReport.matcher_stats(0).name());
+
+        EXPECT_EQ(2, configReport.matcher_stats(1).matched_times());
+        EXPECT_EQ("matcher1", configReport.matcher_stats(1).name());
+    }
+
+    EXPECT_EQ(1, configReport.condition_stats_size());
+    EXPECT_EQ("condition1", configReport.condition_stats(0).name());
+    EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts());
+
+    EXPECT_EQ(1, configReport.metric_stats_size());
+    EXPECT_EQ("metric1", configReport.metric_stats(0).name());
+    EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts());
+
+    // after resetting the stats, some new events come
+    stats.noteMatcherMatched(key, "matcher99");
+    stats.noteConditionDimensionSize(key, "condition99", 300);
+    stats.noteMetricDimensionSize(key, "metric99", 270);
+
+    // now the config stats should only contain the stats about the new event.
+    stats.dumpStats(&output, false);
+    good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+    EXPECT_EQ(1, report.config_stats_size());
+    const auto& configReport2 = report.config_stats(0);
+    EXPECT_EQ(1, configReport2.matcher_stats_size());
+    EXPECT_EQ("matcher99", configReport2.matcher_stats(0).name());
+    EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times());
+
+    EXPECT_EQ(1, configReport2.condition_stats_size());
+    EXPECT_EQ("condition99", configReport2.condition_stats(0).name());
+    EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts());
+
+    EXPECT_EQ(1, configReport2.metric_stats_size());
+    EXPECT_EQ("metric99", configReport2.metric_stats(0).name());
+    EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts());
+}
+
+TEST(StatsdStatsTest, TestAtomLog) {
+    StatsdStats stats;
+    time_t now = time(nullptr);
+    // old event, we get it from the stats buffer. should be ignored.
+    stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, 1000);
+
+    stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 1);
+    stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2);
+    stats.noteAtomLogged(android::util::DROPBOX_ERROR_CHANGED, now + 3);
+    // pulled event, should ignore
+    stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFERRED, now + 4);
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+
+    EXPECT_EQ(2, report.atom_stats_size());
+    bool sensorAtomGood = false;
+    bool dropboxAtomGood = false;
+
+    for (const auto& atomStats : report.atom_stats()) {
+        if (atomStats.tag() == android::util::SENSOR_STATE_CHANGED && atomStats.count() == 2) {
+            sensorAtomGood = true;
+        }
+        if (atomStats.tag() == android::util::DROPBOX_ERROR_CHANGED && atomStats.count() == 1) {
+            dropboxAtomGood = true;
+        }
+    }
+
+    EXPECT_TRUE(dropboxAtomGood);
+    EXPECT_TRUE(sensorAtomGood);
+}
+
+TEST(StatsdStatsTest, TestTimestampThreshold) {
+    StatsdStats stats;
+    vector<int32_t> timestamps;
+    for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
+        timestamps.push_back(i);
+    }
+    ConfigKey key(0, "test");
+    stats.noteConfigReceived(key, 2, 3, 4, 5, true);
+
+    for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
+        stats.noteDataDropped(key, timestamps[i]);
+        stats.noteBroadcastSent(key, timestamps[i]);
+        stats.noteMetricsReportSent(key, timestamps[i]);
+    }
+
+    int32_t newTimestamp = 10000;
+
+    // now it should trigger removing oldest timestamp
+    stats.noteDataDropped(key, 10000);
+    stats.noteBroadcastSent(key, 10000);
+    stats.noteMetricsReportSent(key, 10000);
+
+    EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end());
+    const auto& configStats = stats.mConfigStats[key];
+
+    int maxCount = StatsdStats::kMaxTimestampCount;
+    EXPECT_EQ(maxCount, configStats.broadcast_sent_time_sec_size());
+    EXPECT_EQ(maxCount, configStats.data_drop_time_sec_size());
+    EXPECT_EQ(maxCount, configStats.dump_report_time_sec_size());
+
+    // the oldest timestamp is the second timestamp in history
+    EXPECT_EQ(1, configStats.broadcast_sent_time_sec(0));
+    EXPECT_EQ(1, configStats.broadcast_sent_time_sec(0));
+    EXPECT_EQ(1, configStats.broadcast_sent_time_sec(0));
+
+    // the last timestamp is the newest timestamp.
+    EXPECT_EQ(newTimestamp,
+              configStats.broadcast_sent_time_sec(StatsdStats::kMaxTimestampCount - 1));
+    EXPECT_EQ(newTimestamp, configStats.data_drop_time_sec(StatsdStats::kMaxTimestampCount - 1));
+    EXPECT_EQ(newTimestamp, configStats.dump_report_time_sec(StatsdStats::kMaxTimestampCount - 1));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 35e08af..2cbeaaa 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -32,6 +32,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -48,7 +50,7 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
                                       bucketStartTimeNs);
 
     // 2 events in bucket 1.
@@ -106,7 +108,7 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(metric, 1, wizard, bucketStartTimeNs);
+    CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
 
     countProducer.onConditionChanged(true, bucketStartTimeNs);
     countProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
@@ -161,7 +163,7 @@
 
     EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
 
-    CountMetricProducer countProducer(metric, 1 /*condition tracker index*/, wizard,
+    CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
                                       bucketStartTimeNs);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
@@ -194,14 +196,14 @@
     int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
     int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
 
     CountMetric metric;
     metric.set_name("1");
     metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
                                       bucketStartTimeNs);
     countProducer.addAnomalyTracker(anomalyTracker);
 
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index 18d177c..724ad59 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -32,6 +32,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 TEST(EventMetricProducerTest, TestNoCondition) {
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
@@ -45,7 +47,7 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    EventMetricProducer eventProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+    EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
                                       bucketStartTimeNs);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
@@ -69,7 +71,7 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    EventMetricProducer eventProducer(metric, 1, wizard, bucketStartTimeNs);
+    EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
 
     eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
@@ -111,7 +113,7 @@
 
     EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
 
-    EventMetricProducer eventProducer(metric, 1, wizard, bucketStartTimeNs);
+    EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index b9e2b8a..85f5008 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -148,7 +148,7 @@
     alert.set_metric_name("1");
     alert.set_trigger_if_sum_gt(25);
     alert.set_number_of_buckets(2);
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
     gaugeProducer.addAnomalyTracker(anomalyTracker);
 
     std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index d82ccfe..5d47437 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -36,6 +36,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -45,8 +47,8 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
-                               buckets);
+    MaxDurationTracker tracker(kConfigKey, "metric", "event", wizard, -1, false, bucketStartTimeNs,
+                               bucketSizeNs, {}, buckets);
 
     tracker.noteStart("1", true, bucketStartTimeNs, key1);
     // Event starts again. This would not change anything as it already starts.
@@ -72,8 +74,8 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
-                               buckets);
+    MaxDurationTracker tracker(kConfigKey, "metric", "event", wizard, -1, false, bucketStartTimeNs,
+                               bucketSizeNs, {}, buckets);
 
     tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
 
@@ -100,8 +102,8 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
-                               buckets);
+    MaxDurationTracker tracker(kConfigKey, "metric", "event", wizard, -1, false, bucketStartTimeNs,
+                               bucketSizeNs, {}, buckets);
 
     // The event starts.
     tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
@@ -127,8 +129,8 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {},
-                               buckets);
+    MaxDurationTracker tracker(kConfigKey, "metric", "event", wizard, -1, true, bucketStartTimeNs,
+                               bucketSizeNs, {}, buckets);
 
     // 2 starts
     tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
@@ -168,8 +170,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    MaxDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
-                               buckets);
+    MaxDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, false, bucketStartTimeNs,
+                               bucketSizeNs, {}, buckets);
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
@@ -199,9 +201,9 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
-    MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs,
-                               {anomalyTracker}, buckets);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    MaxDurationTracker tracker(kConfigKey, "metric", "event", wizard, -1, true, bucketStartTimeNs,
+                               bucketSizeNs, {anomalyTracker}, buckets);
 
     tracker.noteStart("1", true, eventStartTimeNs, key1);
     tracker.noteStop("1", eventStartTimeNs + 10, false);
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 54618b5..6913c81 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -34,6 +34,8 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
+
 TEST(OringDurationTrackerTest, TestDurationOverlap) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -47,8 +49,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, false, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -73,8 +75,8 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
@@ -99,8 +101,8 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
@@ -125,8 +127,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -162,8 +164,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, false, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
 
@@ -194,8 +196,8 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, false, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -225,8 +227,8 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
-                                 buckets);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, {}, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
@@ -258,9 +260,9 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
-    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs,
-                                 {anomalyTracker}, buckets);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, true, bucketStartTimeNs,
+                                 bucketSizeNs, {anomalyTracker}, buckets);
 
     // Nothing in the past bucket.
     tracker.noteStart("", true, eventStartTimeNs, key1);
@@ -318,9 +320,9 @@
     uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
     uint64_t bucketSizeNs = 30 * NS_PER_SEC;
 
-    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
-    OringDurationTracker tracker("event", wizard, 1, true /*nesting*/, bucketStartTimeNs,
-                                 bucketSizeNs, {anomalyTracker}, buckets);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
+    OringDurationTracker tracker(kConfigKey, "metric", "event", wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, bucketSizeNs, {anomalyTracker}, buckets);
 
     tracker.noteStart("", true, eventStartTimeNs, key1);
     tracker.noteStop("", eventStartTimeNs + 10, false);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 1ed3636..9d78466 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "metrics_test_helper.h"
 #include "src/metrics/ValueMetricProducer.h"
+#include "metrics_test_helper.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -22,11 +22,11 @@
 
 using namespace testing;
 using android::sp;
+using std::make_shared;
 using std::set;
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
-using std::shared_ptr;
-using std::make_shared;
 
 #ifdef __ANDROID__
 
@@ -34,6 +34,7 @@
 namespace os {
 namespace statsd {
 
+const ConfigKey kConfigKey(0, "test");
 /*
  * Tests pulled atoms with no conditions
  */
@@ -42,7 +43,7 @@
     int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
 
     int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
-    int64_t bucket3StartTimeNs = bucketStartTimeNs + 2*bucketSizeNs;
+    int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 
     ValueMetric metric;
     metric.set_name("1");
@@ -54,12 +55,13 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     // TODO: pending refactor of StatsPullerManager
     // For now we still need this so that it doesn't do real pulling.
-    shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>();
+    shared_ptr<MockStatsPullerManager> pullerManager =
+            make_shared<StrictMock<MockStatsPullerManager>>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
 
-    ValueMetricProducer valueProducer(metric, -1 /*-1 meaning no condition*/, wizard,tagId,
-                                      bucketStartTimeNs, pullerManager);
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+                                      tagId, bucketStartTimeNs, pullerManager);
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
@@ -144,41 +146,43 @@
     int tagId = 1;
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>();
+    shared_ptr<MockStatsPullerManager> pullerManager =
+            make_shared<StrictMock<MockStatsPullerManager>>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
 
-    EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Invoke([] (int tagId, vector<std::shared_ptr<LogEvent>>* data) {
-        int64_t bucketStartTimeNs = 10000000000;
-        int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+    EXPECT_CALL(*pullerManager, Pull(tagId, _))
+            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+                int64_t bucketStartTimeNs = 10000000000;
+                int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
 
-        int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
-        int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
-        data->clear();
-        shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-        event->write(1);
-        event->write(100);
-        event->init();
-        data->push_back(event);
-        return true;
-    }))
-    .WillOnce(Invoke([] (int tagId, vector<std::shared_ptr<LogEvent>>* data) {
-        int64_t bucketStartTimeNs = 10000000000;
-        int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+                int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+                int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+                data->clear();
+                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+                event->write(1);
+                event->write(100);
+                event->init();
+                data->push_back(event);
+                return true;
+            }))
+            .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+                int64_t bucketStartTimeNs = 10000000000;
+                int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
 
-        int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
-        int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
-        data->clear();
-        shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
-        event->write(1);
-        event->write(120);
-        event->init();
-        data->push_back(event);
-        return true;
-    }));
+                int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+                int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+                data->clear();
+                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
+                event->write(1);
+                event->write(120);
+                event->init();
+                data->push_back(event);
+                return true;
+            }));
 
-    ValueMetricProducer valueProducer(metric, 1, wizard,tagId,
-                                      bucketStartTimeNs, pullerManager);
+    ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs,
+                                      pullerManager);
 
     valueProducer.onConditionChanged(true, bucketStartTimeNs + 10);
 
@@ -241,10 +245,11 @@
     int tagId = 1;
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>();
+    shared_ptr<MockStatsPullerManager> pullerManager =
+            make_shared<StrictMock<MockStatsPullerManager>>();
 
-    ValueMetricProducer valueProducer(metric, -1, wizard,-1,
-                                      bucketStartTimeNs, pullerManager);
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs,
+                                      pullerManager);
 
     shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
     event1->write(1);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index d8c7929..b43cf27 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -4,16 +4,16 @@
 }
 
 // only used by key_store_service
-cc_library_static {
+cc_library_shared {
     name: "libkeystore_aidl",
     srcs: ["android/security/IKeystoreService.aidl"],
     aidl: {
         export_aidl_headers: true,
-        include_dirs: ["frameworks/base/core/java/"],
+        include_dirs: [
+            "frameworks/base/core/java/",
+            "system/security/keystore/",
+        ],
     },
-    header_libs: [
-        "libkeystore_headers",
-    ],
     shared_libs: [
         "libbinder",
         "libcutils",
@@ -22,7 +22,12 @@
         "libhidltransport",
         "libhwbinder",
         "liblog",
+        "libkeystore_parcelables",
         "libselinux",
         "libutils",
     ],
+    export_shared_lib_headers: [
+        "libbinder",
+        "libkeystore_parcelables",
+    ],
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 06a9b06..e0d60cd 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -16,6 +16,8 @@
 
 package android.accessibilityservice;
 
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -47,8 +49,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
-
 /**
  * This class describes an {@link AccessibilityService}. The system notifies an
  * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
@@ -554,7 +554,7 @@
     }
 
     /**
-     * Updates the properties that an AccessibilitySerivice can change dynamically.
+     * Updates the properties that an AccessibilityService can change dynamically.
      *
      * @param other The info from which to update the properties.
      *
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a46b3c7..d7efa91 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -283,4 +283,20 @@
      * @param token The IApplicationToken for the activity
      */
     public abstract void setFocusedActivity(IBinder token);
+
+    /**
+     * Set a uid that is allowed to bypass stopped app switches, launching an app
+     * whenever it wants.
+     *
+     * @param type Type of the caller -- unique string the caller supplies to identify itself
+     * and disambiguate with other calles.
+     * @param uid The uid of the app to be allowed, or -1 to clear the uid for this type.
+     * @param userId The user it is allowed for.
+     */
+    public abstract void setAllowAppSwitches(@NonNull String type, int uid, int userId);
+
+    /**
+     * @return true if runtime was restarted, false if it's normal boot
+     */
+    public abstract boolean isRuntimeRestarted();
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 95c5fb5..ffd012d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -23,6 +23,8 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.BackupAgent;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
@@ -174,7 +176,7 @@
  *
  * {@hide}
  */
-public final class ActivityThread {
+public final class ActivityThread extends ClientTransactionHandler {
     /** @hide */
     public static final String TAG = "ActivityThread";
     private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
@@ -401,8 +403,8 @@
                     throw new IllegalStateException(
                             "Received config update for non-existing activity");
                 }
-                activity.mMainThread.handleActivityConfigurationChanged(
-                        new ActivityConfigChangeData(token, overrideConfig), newDisplayId);
+                activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+                        newDisplayId);
             };
         }
 
@@ -469,16 +471,6 @@
         }
     }
 
-    static final class NewIntentData {
-        List<ReferrerIntent> intents;
-        IBinder token;
-        boolean andPause;
-        public String toString() {
-            return "NewIntentData{intents=" + intents + " token=" + token
-                    + " andPause=" + andPause +"}";
-        }
-    }
-
     static final class ReceiverData extends BroadcastReceiver.PendingResult {
         public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
                 boolean ordered, boolean sticky, IBinder token, int sendingUser) {
@@ -644,14 +636,6 @@
         String[] args;
     }
 
-    static final class ResultData {
-        IBinder token;
-        List<ResultInfo> results;
-        public String toString() {
-            return "ResultData{token=" + token + " results" + results + "}";
-        }
-    }
-
     static final class ContextCleanupInfo {
         ContextImpl context;
         String what;
@@ -679,15 +663,6 @@
         int flags;
     }
 
-    static final class ActivityConfigChangeData {
-        final IBinder activityToken;
-        final Configuration overrideConfig;
-        public ActivityConfigChangeData(IBinder token, Configuration config) {
-            activityToken = token;
-            overrideConfig = config;
-        }
-    }
-
     private class ApplicationThread extends IApplicationThread.Stub {
         private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
 
@@ -702,93 +677,10 @@
             }
         }
 
-        public final void schedulePauseActivity(IBinder token, boolean finished,
-                boolean userLeaving, int configChanges, boolean dontReport) {
-            int seq = getLifecycleSeq();
-            if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this
-                    + " operation received seq: " + seq);
-            sendMessage(
-                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
-                    token,
-                    (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),
-                    configChanges,
-                    seq);
-        }
-
-        public final void scheduleStopActivity(IBinder token, boolean showWindow,
-                int configChanges) {
-            int seq = getLifecycleSeq();
-            if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this
-                    + " operation received seq: " + seq);
-            sendMessage(
-                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
-                token, 0, configChanges, seq);
-        }
-
-        public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
-            sendMessage(
-                showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
-                token);
-        }
-
         public final void scheduleSleeping(IBinder token, boolean sleeping) {
             sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
         }
 
-        public final void scheduleResumeActivity(IBinder token, int processState,
-                boolean isForward, Bundle resumeArgs) {
-            int seq = getLifecycleSeq();
-            if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
-                    + " operation received seq: " + seq);
-            updateProcessState(processState, false);
-            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
-        }
-
-        public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
-            ResultData res = new ResultData();
-            res.token = token;
-            res.results = results;
-            sendMessage(H.SEND_RESULT, res);
-        }
-
-        // we use token to identify this activity without having to send the
-        // activity itself back to the activity manager. (matters more with ipc)
-        @Override
-        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
-                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
-                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
-                int procState, Bundle state, PersistableBundle persistentState,
-                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
-                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
-
-            updateProcessState(procState, false);
-
-            ActivityClientRecord r = new ActivityClientRecord();
-
-            r.token = token;
-            r.ident = ident;
-            r.intent = intent;
-            r.referrer = referrer;
-            r.voiceInteractor = voiceInteractor;
-            r.activityInfo = info;
-            r.compatInfo = compatInfo;
-            r.state = state;
-            r.persistentState = persistentState;
-
-            r.pendingResults = pendingResults;
-            r.pendingIntents = pendingNewIntents;
-
-            r.startsNotResumed = notResumed;
-            r.isForward = isForward;
-
-            r.profilerInfo = profilerInfo;
-
-            r.overrideConfig = overrideConfig;
-            updatePendingConfiguration(curConfig);
-
-            sendMessage(H.LAUNCH_ACTIVITY, r);
-        }
-
         @Override
         public final void scheduleRelaunchActivity(IBinder token,
                 List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -798,22 +690,6 @@
                     configChanges, notResumed, config, overrideConfig, true, preserveWindow);
         }
 
-        public final void scheduleNewIntent(
-                List<ReferrerIntent> intents, IBinder token, boolean andPause) {
-            NewIntentData data = new NewIntentData();
-            data.intents = intents;
-            data.token = token;
-            data.andPause = andPause;
-
-            sendMessage(H.NEW_INTENT, data);
-        }
-
-        public final void scheduleDestroyActivity(IBinder token, boolean finishing,
-                int configChanges) {
-            sendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
-                    configChanges);
-        }
-
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                 boolean sync, int sendingUser, int processState) {
@@ -949,11 +825,6 @@
             sendMessage(H.SUICIDE, null);
         }
 
-        public void scheduleConfigurationChanged(Configuration config) {
-            updatePendingConfiguration(config);
-            sendMessage(H.CONFIGURATION_CHANGED, config);
-        }
-
         public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
             sendMessage(H.APPLICATION_INFO_CHANGED, ai);
         }
@@ -1016,20 +887,6 @@
         }
 
         @Override
-        public void scheduleActivityConfigurationChanged(
-                IBinder token, Configuration overrideConfig) {
-            sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
-                    new ActivityConfigChangeData(token, overrideConfig));
-        }
-
-        @Override
-        public void scheduleActivityMovedToDisplay(IBinder token, int displayId,
-                Configuration overrideConfig) {
-            sendMessage(H.ACTIVITY_MOVED_TO_DISPLAY,
-                    new ActivityConfigChangeData(token, overrideConfig), displayId);
-        }
-
-        @Override
         public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
             sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
         }
@@ -1427,26 +1284,6 @@
         }
 
         @Override
-        public void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
-                Configuration overrideConfig) throws RemoteException {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = token;
-            args.arg2 = overrideConfig;
-            args.argi1 = isInMultiWindowMode ? 1 : 0;
-            sendMessage(H.MULTI_WINDOW_MODE_CHANGED, args);
-        }
-
-        @Override
-        public void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
-                Configuration overrideConfig) throws RemoteException {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = token;
-            args.arg2 = overrideConfig;
-            args.argi1 = isInPipMode ? 1 : 0;
-            sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, args);
-        }
-
-        @Override
         public void scheduleLocalVoiceInteractionStarted(IBinder token,
                 IVoiceInteractor voiceInteractor) throws RemoteException {
             SomeArgs args = SomeArgs.obtain();
@@ -1459,28 +1296,33 @@
         public void handleTrustStorageUpdate() {
             NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
         }
+
+        @Override
+        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+            ActivityThread.this.scheduleTransaction(transaction);
+        }
     }
 
-    private int getLifecycleSeq() {
+    @Override
+    public void updatePendingConfiguration(Configuration config) {
+        mAppThread.updatePendingConfiguration(config);
+    }
+
+    @Override
+    public void updateProcessState(int processState, boolean fromIpc) {
+        mAppThread.updateProcessState(processState, fromIpc);
+    }
+
+    @Override
+    public int getLifecycleSeq() {
         synchronized (mResourcesManager) {
             return mLifecycleSeq++;
         }
     }
 
-    private class H extends Handler {
-        public static final int LAUNCH_ACTIVITY         = 100;
-        public static final int PAUSE_ACTIVITY          = 101;
-        public static final int PAUSE_ACTIVITY_FINISHING= 102;
-        public static final int STOP_ACTIVITY_SHOW      = 103;
-        public static final int STOP_ACTIVITY_HIDE      = 104;
-        public static final int SHOW_WINDOW             = 105;
-        public static final int HIDE_WINDOW             = 106;
-        public static final int RESUME_ACTIVITY         = 107;
-        public static final int SEND_RESULT             = 108;
-        public static final int DESTROY_ACTIVITY        = 109;
+    class H extends Handler {
         public static final int BIND_APPLICATION        = 110;
         public static final int EXIT_APPLICATION        = 111;
-        public static final int NEW_INTENT              = 112;
         public static final int RECEIVER                = 113;
         public static final int CREATE_SERVICE          = 114;
         public static final int SERVICE_ARGS            = 115;
@@ -1493,7 +1335,6 @@
         public static final int UNBIND_SERVICE          = 122;
         public static final int DUMP_SERVICE            = 123;
         public static final int LOW_MEMORY              = 124;
-        public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
         public static final int RELAUNCH_ACTIVITY       = 126;
         public static final int PROFILER_CONTROL        = 127;
         public static final int CREATE_BACKUP_AGENT     = 128;
@@ -1518,30 +1359,17 @@
         public static final int ENTER_ANIMATION_COMPLETE = 149;
         public static final int START_BINDER_TRACKING = 150;
         public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
-        public static final int MULTI_WINDOW_MODE_CHANGED = 152;
-        public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
         public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
         public static final int ATTACH_AGENT = 155;
         public static final int APPLICATION_INFO_CHANGED = 156;
-        public static final int ACTIVITY_MOVED_TO_DISPLAY = 157;
         public static final int RUN_ISOLATED_ENTRY_POINT = 158;
+        public static final int EXECUTE_TRANSACTION = 159;
 
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
-                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
-                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
-                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
-                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
-                    case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
-                    case SHOW_WINDOW: return "SHOW_WINDOW";
-                    case HIDE_WINDOW: return "HIDE_WINDOW";
-                    case RESUME_ACTIVITY: return "RESUME_ACTIVITY";
-                    case SEND_RESULT: return "SEND_RESULT";
-                    case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY";
                     case BIND_APPLICATION: return "BIND_APPLICATION";
                     case EXIT_APPLICATION: return "EXIT_APPLICATION";
-                    case NEW_INTENT: return "NEW_INTENT";
                     case RECEIVER: return "RECEIVER";
                     case CREATE_SERVICE: return "CREATE_SERVICE";
                     case SERVICE_ARGS: return "SERVICE_ARGS";
@@ -1553,8 +1381,6 @@
                     case UNBIND_SERVICE: return "UNBIND_SERVICE";
                     case DUMP_SERVICE: return "DUMP_SERVICE";
                     case LOW_MEMORY: return "LOW_MEMORY";
-                    case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
-                    case ACTIVITY_MOVED_TO_DISPLAY: return "ACTIVITY_MOVED_TO_DISPLAY";
                     case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
                     case PROFILER_CONTROL: return "PROFILER_CONTROL";
                     case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
@@ -1577,12 +1403,11 @@
                     case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
                     case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
                     case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
-                    case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
-                    case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
                     case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
                     case ATTACH_AGENT: return "ATTACH_AGENT";
                     case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
                     case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
+                    case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                 }
             }
             return Integer.toString(code);
@@ -1590,76 +1415,12 @@
         public void handleMessage(Message msg) {
             if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
             switch (msg.what) {
-                case LAUNCH_ACTIVITY: {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
-                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
-
-                    r.packageInfo = getPackageInfoNoCheck(
-                            r.activityInfo.applicationInfo, r.compatInfo);
-                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                } break;
                 case RELAUNCH_ACTIVITY: {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                     handleRelaunchActivity(r);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                 } break;
-                case PAUSE_ACTIVITY: {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handlePauseActivity((IBinder) args.arg1, false,
-                            (args.argi1 & USER_LEAVING) != 0, args.argi2,
-                            (args.argi1 & DONT_REPORT) != 0, args.argi3);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                } break;
-                case PAUSE_ACTIVITY_FINISHING: {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
-                            args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                } break;
-                case STOP_ACTIVITY_SHOW: {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                } break;
-                case STOP_ACTIVITY_HIDE: {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                } break;
-                case SHOW_WINDOW:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
-                    handleWindowVisibility((IBinder)msg.obj, true);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case HIDE_WINDOW:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");
-                    handleWindowVisibility((IBinder)msg.obj, false);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case RESUME_ACTIVITY:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
-                            args.argi3, "RESUME_ACTIVITY");
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case SEND_RESULT:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
-                    handleSendResult((ResultData)msg.obj);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case DESTROY_ACTIVITY:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
-                    handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
-                            msg.arg2, false);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
                 case BIND_APPLICATION:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                     AppBindData data = (AppBindData)msg.obj;
@@ -1672,11 +1433,6 @@
                     }
                     Looper.myLooper().quit();
                     break;
-                case NEW_INTENT:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
-                    handleNewIntent((NewIntentData)msg.obj);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
                 case RECEIVER:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                     handleReceiver((ReceiverData)msg.obj);
@@ -1708,15 +1464,7 @@
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
                 case CONFIGURATION_CHANGED:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
-                    mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
-                    mUpdatingSystemConfig = true;
-                    try {
-                        handleConfigurationChanged((Configuration) msg.obj, null);
-                    } finally {
-                        mUpdatingSystemConfig = false;
-                    }
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    handleConfigurationChanged((Configuration) msg.obj);
                     break;
                 case CLEAN_UP_CONTEXT:
                     ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
@@ -1733,18 +1481,6 @@
                     handleLowMemory();
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
-                case ACTIVITY_CONFIGURATION_CHANGED:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
-                    handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
-                            INVALID_DISPLAY);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case ACTIVITY_MOVED_TO_DISPLAY:
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
-                    handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
-                            msg.arg1 /* displayId */);
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
                 case PROFILER_CONTROL:
                     handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
                     break;
@@ -1828,16 +1564,6 @@
                 case STOP_BINDER_TRACKING_AND_DUMP:
                     handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
                     break;
-                case MULTI_WINDOW_MODE_CHANGED:
-                    handleMultiWindowModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
-                            ((SomeArgs) msg.obj).argi1 == 1,
-                            (Configuration) ((SomeArgs) msg.obj).arg2);
-                    break;
-                case PICTURE_IN_PICTURE_MODE_CHANGED:
-                    handlePictureInPictureModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
-                            ((SomeArgs) msg.obj).argi1 == 1,
-                            (Configuration) ((SomeArgs) msg.obj).arg2);
-                    break;
                 case LOCAL_VOICE_INTERACTION_STARTED:
                     handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
                             (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
@@ -1857,6 +1583,9 @@
                     handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
                             (String[]) ((SomeArgs) msg.obj).arg2);
                     break;
+                case EXECUTE_TRANSACTION:
+                    ((ClientTransaction) msg.obj).execute(ActivityThread.this);
+                    break;
             }
             Object obj = msg.obj;
             if (obj instanceof SomeArgs) {
@@ -2601,10 +2330,16 @@
                 + " req=" + requestCode + " res=" + resultCode + " data=" + data);
         ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
         list.add(new ResultInfo(id, requestCode, resultCode, data));
-        mAppThread.scheduleSendResult(token, list);
+        final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token);
+        clientTransaction.addCallback(new ActivityResultItem(list));
+        try {
+            mAppThread.scheduleTransaction(clientTransaction);
+        } catch (RemoteException e) {
+            // Local scheduling
+        }
     }
 
-    private void sendMessage(int what, Object obj) {
+    void sendMessage(int what, Object obj) {
         sendMessage(what, obj, 0, 0, false);
     }
 
@@ -2844,6 +2579,37 @@
         return appContext;
     }
 
+    @Override
+    public void handleLaunchActivity(IBinder token, Intent intent, int ident, ActivityInfo info,
+            Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer,
+            IVoiceInteractor voiceInteractor, Bundle state, PersistableBundle persistentState,
+            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+            boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
+        ActivityClientRecord r = new ActivityClientRecord();
+
+        r.token = token;
+        r.ident = ident;
+        r.intent = intent;
+        r.referrer = referrer;
+        r.voiceInteractor = voiceInteractor;
+        r.activityInfo = info;
+        r.compatInfo = compatInfo;
+        r.state = state;
+        r.persistentState = persistentState;
+
+        r.pendingResults = pendingResults;
+        r.pendingIntents = pendingNewIntents;
+
+        r.startsNotResumed = notResumed;
+        r.isForward = isForward;
+
+        r.profilerInfo = profilerInfo;
+
+        r.overrideConfig = overrideConfig;
+        r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
+        handleLaunchActivity(r, null /* customIntent */, "LAUNCH_ACTIVITY");
+    }
+
     private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
@@ -2974,8 +2740,9 @@
         }
     }
 
-    private void handleNewIntent(NewIntentData data) {
-        performNewIntents(data.token, data.intents, data.andPause);
+    @Override
+    public void handleNewIntent(IBinder token, List<ReferrerIntent> intents, boolean andPause) {
+        performNewIntents(token, intents, andPause);
     }
 
     public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
@@ -3096,7 +2863,8 @@
         }
     }
 
-    private void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+    @Override
+    public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
             Configuration overrideConfig) {
         final ActivityClientRecord r = mActivities.get(token);
         if (r != null) {
@@ -3108,7 +2876,8 @@
         }
     }
 
-    private void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+    @Override
+    public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
             Configuration overrideConfig) {
         final ActivityClientRecord r = mActivities.get(token);
         if (r != null) {
@@ -3619,8 +3388,9 @@
         r.mPendingRemoveWindowManager = null;
     }
 
-    final void handleResumeActivity(IBinder token,
-            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
+    @Override
+    public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+            boolean reallyResume, int seq, String reason) {
         ActivityClientRecord r = mActivities.get(token);
         if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
             return;
@@ -3823,7 +3593,8 @@
         return thumbnail;
     }
 
-    private void handlePauseActivity(IBinder token, boolean finished,
+    @Override
+    public void handlePauseActivity(IBinder token, boolean finished,
             boolean userLeaving, int configChanges, boolean dontReport, int seq) {
         ActivityClientRecord r = mActivities.get(token);
         if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
@@ -4087,7 +3858,8 @@
         }
     }
 
-    private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
+    @Override
+    public void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
         ActivityClientRecord r = mActivities.get(token);
         if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) {
             return;
@@ -4142,7 +3914,8 @@
         }
     }
 
-    private void handleWindowVisibility(IBinder token, boolean show) {
+    @Override
+    public void handleWindowVisibility(IBinder token, boolean show) {
         ActivityClientRecord r = mActivities.get(token);
 
         if (r == null) {
@@ -4288,8 +4061,9 @@
         }
     }
 
-    private void handleSendResult(ResultData res) {
-        ActivityClientRecord r = mActivities.get(res.token);
+    @Override
+    public void handleSendResult(IBinder token, List<ResultInfo> results) {
+        ActivityClientRecord r = mActivities.get(token);
         if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
         if (r != null) {
             final boolean resumed = !r.paused;
@@ -4323,7 +4097,7 @@
                 }
             }
             checkAndBlockForNetworkAccess();
-            deliverResults(r, res.results);
+            deliverResults(r, results);
             if (resumed) {
                 r.activity.performResume();
                 r.activity.mTemporaryPause = false;
@@ -4410,8 +4184,9 @@
         return component == null ? "[Unknown]" : component.toShortString();
     }
 
-    private void handleDestroyActivity(IBinder token, boolean finishing,
-            int configChanges, boolean getNonConfigInstance) {
+    @Override
+    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+            boolean getNonConfigInstance) {
         ActivityClientRecord r = performDestroyActivity(token, finishing,
                 configChanges, getNonConfigInstance);
         if (r != null) {
@@ -4982,7 +4757,20 @@
         return config;
     }
 
-    final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
+    @Override
+    public void handleConfigurationChanged(Configuration config) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
+        mCurDefaultDisplayDpi = config.densityDpi;
+        mUpdatingSystemConfig = true;
+        try {
+            handleConfigurationChanged(config, null /* compat */);
+        } finally {
+            mUpdatingSystemConfig = false;
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
 
         int configDiff = 0;
 
@@ -5113,12 +4901,15 @@
 
     /**
      * Handle new activity configuration and/or move to a different display.
-     * @param data Configuration update data.
+     * @param activityToken Target activity token.
+     * @param overrideConfig Activity override config.
      * @param displayId Id of the display where activity was moved to, -1 if there was no move and
      *                  value didn't change.
      */
-    void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) {
-        ActivityClientRecord r = mActivities.get(data.activityToken);
+    @Override
+    public void handleActivityConfigurationChanged(IBinder activityToken,
+            Configuration overrideConfig, int displayId) {
+        ActivityClientRecord r = mActivities.get(activityToken);
         // Check input params.
         if (r == null || r.activity == null) {
             if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
@@ -5128,14 +4919,14 @@
                 && displayId != r.activity.getDisplay().getDisplayId();
 
         // Perform updates.
-        r.overrideConfig = data.overrideConfig;
+        r.overrideConfig = overrideConfig;
         final ViewRootImpl viewRoot = r.activity.mDecor != null
             ? r.activity.mDecor.getViewRootImpl() : null;
 
         if (movedToDifferentDisplay) {
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
                     + r.activityInfo.name + ", displayId=" + displayId
-                    + ", config=" + data.overrideConfig);
+                    + ", config=" + overrideConfig);
 
             final Configuration reportedConfig = performConfigurationChangedForActivity(r,
                     mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
@@ -5144,7 +4935,7 @@
             }
         } else {
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
-                    + r.activityInfo.name + ", config=" + data.overrideConfig);
+                    + r.activityInfo.name + ", config=" + overrideConfig);
             performConfigurationChangedForActivity(r, mCompatConfiguration);
         }
         // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7a4c00f..005b7c3 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2462,7 +2462,8 @@
         if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
             Bitmap bitmap = getUserManager().getUserIcon(itemInfo.showUserIcon);
             if (bitmap == null) {
-                return UserIcons.getDefaultUserIcon(itemInfo.showUserIcon, /* light= */ false);
+                return UserIcons.getDefaultUserIcon(
+                        mContext.getResources(), itemInfo.showUserIcon, /* light= */ false);
             }
             return new BitmapDrawable(bitmap);
         }
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
new file mode 100644
index 0000000..f7f4c71
--- /dev/null
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.app.servertransaction.ClientTransaction;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
+ * can perform on client.
+ * @hide
+ */
+public abstract class ClientTransactionHandler {
+
+    // Schedule phase related logic and handlers.
+
+    /** Prepare and schedule transaction for execution. */
+    void scheduleTransaction(ClientTransaction transaction) {
+        transaction.prepare(this);
+        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
+    }
+
+    abstract void sendMessage(int what, Object obj);
+
+
+    // Prepare phase related logic and handlers. Methods that inform about about pending changes or
+    // do other internal bookkeeping.
+
+    /** Get current lifecycle request number to maintain correct ordering. */
+    public abstract int getLifecycleSeq();
+
+    /** Set pending config in case it will be updated by other transaction item. */
+    public abstract void updatePendingConfiguration(Configuration config);
+
+    /** Set current process state. */
+    public abstract void updateProcessState(int processState, boolean fromIpc);
+
+
+    // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
+    // and deliver callbacks.
+
+    /** Destroy the activity. */
+    public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+            boolean getNonConfigInstance);
+
+    /** Pause the activity. */
+    public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+            int configChanges, boolean dontReport, int seq);
+
+    /** Resume the activity. */
+    public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+            boolean reallyResume, int seq, String reason);
+
+    /** Stop the activity. */
+    public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+            int seq);
+
+    /** Deliver activity (override) configuration change. */
+    public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+            Configuration overrideConfig, int displayId);
+
+    /** Deliver result from another activity. */
+    public abstract void handleSendResult(IBinder token, List<ResultInfo> results);
+
+    /** Deliver multi-window mode change notification. */
+    public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+            Configuration overrideConfig);
+
+    /** Deliver new intent. */
+    public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents,
+            boolean andPause);
+
+    /** Deliver picture-in-picture mode change notification. */
+    public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+            Configuration overrideConfig);
+
+    /** Update window visibility. */
+    public abstract void handleWindowVisibility(IBinder token, boolean show);
+
+    /** Perform activity launch. */
+    public abstract void handleLaunchActivity(IBinder token, Intent intent, int ident,
+            ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
+            String referrer, IVoiceInteractor voiceInteractor, Bundle state,
+            PersistableBundle persistentState, List<ResultInfo> pendingResults,
+            List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+            ProfilerInfo profilerInfo);
+
+    /** Deliver app configuration change notification. */
+    public abstract void handleConfigurationChanged(Configuration config);
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5f34322..b0d020a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -59,11 +59,10 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -2457,7 +2456,8 @@
      * unable to create, they are filtered by replacing with {@code null}.
      */
     private File[] ensureExternalDirsExistOrFilter(File[] dirs) {
-        File[] result = new File[dirs.length];
+        final StorageManager sm = getSystemService(StorageManager.class);
+        final File[] result = new File[dirs.length];
         for (int i = 0; i < dirs.length; i++) {
             File dir = dirs[i];
             if (!dir.exists()) {
@@ -2466,15 +2466,8 @@
                     if (!dir.exists()) {
                         // Failing to mkdir() may be okay, since we might not have
                         // enough permissions; ask vold to create on our behalf.
-                        final IStorageManager storageManager = IStorageManager.Stub.asInterface(
-                                ServiceManager.getService("mount"));
                         try {
-                            final int res = storageManager.mkdirs(
-                                    getPackageName(), dir.getAbsolutePath());
-                            if (res != 0) {
-                                Log.w(TAG, "Failed to ensure " + dir + ": " + res);
-                                dir = null;
-                            }
+                            sm.mkdirs(dir);
                         } catch (Exception e) {
                             Log.w(TAG, "Failed to ensure " + dir + ": " + e);
                             dir = null;
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 487a94a..b25d778 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -20,6 +20,7 @@
 import android.app.IUiAutomationConnection;
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
+import android.app.servertransaction.ClientTransaction;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.Intent;
@@ -52,24 +53,6 @@
  * {@hide}
  */
 oneway interface IApplicationThread {
-    void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving,
-            int configChanges, boolean dontReport);
-    void scheduleStopActivity(IBinder token, boolean showWindow,
-            int configChanges);
-    void scheduleWindowVisibility(IBinder token, boolean showWindow);
-    void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
-            in Bundle resumeArgs);
-    void scheduleSendResult(IBinder token, in List<ResultInfo> results);
-    void scheduleLaunchActivity(in Intent intent, IBinder token, int ident,
-            in ActivityInfo info, in Configuration curConfig, in Configuration overrideConfig,
-            in CompatibilityInfo compatInfo, in String referrer, IVoiceInteractor voiceInteractor,
-            int procState, in Bundle state, in PersistableBundle persistentState,
-            in List<ResultInfo> pendingResults, in List<ReferrerIntent> pendingNewIntents,
-            boolean notResumed, boolean isForward, in ProfilerInfo profilerInfo);
-    void scheduleNewIntent(
-            in List<ReferrerIntent> intent, IBinder token, boolean andPause);
-    void scheduleDestroyActivity(IBinder token, boolean finished,
-            int configChanges);
     void scheduleReceiver(in Intent intent, in ActivityInfo info,
             in CompatibilityInfo compatInfo,
             int resultCode, in String data, in Bundle extras, boolean sync,
@@ -87,7 +70,6 @@
             in Bundle coreSettings, in String buildSerial);
     void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
     void scheduleExit();
-    void scheduleConfigurationChanged(in Configuration config);
     void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
     void updateTimeZone();
     void processInBackground();
@@ -101,9 +83,6 @@
             int resultCode, in String data, in Bundle extras, boolean ordered,
             boolean sticky, int sendingUser, int processState);
     void scheduleLowMemory();
-    void scheduleActivityConfigurationChanged(IBinder token, in Configuration overrideConfig);
-    void scheduleActivityMovedToDisplay(IBinder token, int displayId,
-            in Configuration overrideConfig);
     void scheduleRelaunchActivity(IBinder token, in List<ResultInfo> pendingResults,
             in List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
             in Configuration config, in Configuration overrideConfig, boolean preserveWindow);
@@ -146,14 +125,11 @@
     void notifyCleartextNetwork(in byte[] firstPacket);
     void startBinderTracking();
     void stopBinderTrackingAndDump(in ParcelFileDescriptor fd);
-    void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
-            in Configuration newConfig);
-    void schedulePictureInPictureModeChanged(IBinder token, boolean isInPictureInPictureMode,
-            in Configuration newConfig);
     void scheduleLocalVoiceInteractionStarted(IBinder token,
             IVoiceInteractor voiceInteractor);
     void handleTrustStorageUpdate();
     void attachAgent(String path);
     void scheduleApplicationInfoChanged(in ApplicationInfo ai);
     void setNetworkBlockSeq(long procStateSeq);
+    void scheduleTransaction(in ClientTransaction transaction);
 }
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index fad4798..d523427 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -22,6 +22,7 @@
 import android.util.Slog;
 
 import java.io.IOException;
+import java.util.Objects;
 
 /**
  * System private API for passing profiler settings.
@@ -132,4 +133,32 @@
         streamingOutput = in.readInt() != 0;
         agent = in.readString();
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ProfilerInfo other = (ProfilerInfo) o;
+        // TODO: Also check #profileFd for equality.
+        return Objects.equals(profileFile, other.profileFile)
+                && autoStopProfiler == other.autoStopProfiler
+                && samplingInterval == other.samplingInterval
+                && streamingOutput == other.streamingOutput
+                && Objects.equals(agent, other.agent);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(profileFile);
+        result = 31 * result + samplingInterval;
+        result = 31 * result + (autoStopProfiler ? 1 : 0);
+        result = 31 * result + (streamingOutput ? 1 : 0);
+        result = 31 * result + Objects.hashCode(agent);
+        return result;
+    }
 }
diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java
index 5e0867c..d5af08a 100644
--- a/core/java/android/app/ResultInfo.java
+++ b/core/java/android/app/ResultInfo.java
@@ -20,6 +20,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * {@hide}
  */
@@ -79,4 +81,29 @@
             mData = null;
         }
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof ResultInfo)) {
+            return false;
+        }
+        final ResultInfo other = (ResultInfo) obj;
+        final boolean intentsEqual = mData == null ? (other.mData == null)
+                : mData.filterEquals(other.mData);
+        return intentsEqual && Objects.equals(mResultWho, other.mResultWho)
+                && mResultCode == other.mResultCode
+                && mRequestCode == other.mRequestCode;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mRequestCode;
+        result = 31 * result + mResultCode;
+        result = 31 * result + Objects.hashCode(mResultWho);
+        if (mData != null) {
+            result = 31 * result + mData.filterHashCode();
+        }
+        return result;
+    }
 }
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 6ea0825..8c47598 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -34,8 +34,6 @@
 
 import libcore.io.IoUtils;
 
-import com.google.android.collect.Maps;
-
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedInputStream;
@@ -139,7 +137,7 @@
             Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
         }
 
-        Map map = null;
+        Map<String, Object> map = null;
         StructStat stat = null;
         try {
             stat = Os.stat(mFile.getPath());
@@ -148,7 +146,7 @@
                 try {
                     str = new BufferedInputStream(
                             new FileInputStream(mFile), 16*1024);
-                    map = XmlUtils.readMapXml(str);
+                    map = (Map<String, Object>) XmlUtils.readMapXml(str);
                 } catch (Exception e) {
                     Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
                 } finally {
@@ -214,12 +212,14 @@
         }
     }
 
+    @Override
     public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
         synchronized(mLock) {
             mListeners.put(listener, CONTENT);
         }
     }
 
+    @Override
     public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
         synchronized(mLock) {
             mListeners.remove(listener);
@@ -241,6 +241,7 @@
         }
     }
 
+    @Override
     public Map<String, ?> getAll() {
         synchronized (mLock) {
             awaitLoadedLocked();
@@ -249,6 +250,7 @@
         }
     }
 
+    @Override
     @Nullable
     public String getString(String key, @Nullable String defValue) {
         synchronized (mLock) {
@@ -258,6 +260,7 @@
         }
     }
 
+    @Override
     @Nullable
     public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
         synchronized (mLock) {
@@ -267,6 +270,7 @@
         }
     }
 
+    @Override
     public int getInt(String key, int defValue) {
         synchronized (mLock) {
             awaitLoadedLocked();
@@ -274,6 +278,7 @@
             return v != null ? v : defValue;
         }
     }
+    @Override
     public long getLong(String key, long defValue) {
         synchronized (mLock) {
             awaitLoadedLocked();
@@ -281,6 +286,7 @@
             return v != null ? v : defValue;
         }
     }
+    @Override
     public float getFloat(String key, float defValue) {
         synchronized (mLock) {
             awaitLoadedLocked();
@@ -288,6 +294,7 @@
             return v != null ? v : defValue;
         }
     }
+    @Override
     public boolean getBoolean(String key, boolean defValue) {
         synchronized (mLock) {
             awaitLoadedLocked();
@@ -296,6 +303,7 @@
         }
     }
 
+    @Override
     public boolean contains(String key) {
         synchronized (mLock) {
             awaitLoadedLocked();
@@ -303,6 +311,7 @@
         }
     }
 
+    @Override
     public Editor edit() {
         // TODO: remove the need to call awaitLoadedLocked() when
         // requesting an editor.  will require some work on the
@@ -347,71 +356,81 @@
     }
 
     public final class EditorImpl implements Editor {
-        private final Object mLock = new Object();
+        private final Object mEditorLock = new Object();
 
-        @GuardedBy("mLock")
-        private final Map<String, Object> mModified = Maps.newHashMap();
+        @GuardedBy("mEditorLock")
+        private final Map<String, Object> mModified = new HashMap<>();
 
-        @GuardedBy("mLock")
+        @GuardedBy("mEditorLock")
         private boolean mClear = false;
 
+        @Override
         public Editor putString(String key, @Nullable String value) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key, value);
                 return this;
             }
         }
+        @Override
         public Editor putStringSet(String key, @Nullable Set<String> values) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key,
                         (values == null) ? null : new HashSet<String>(values));
                 return this;
             }
         }
+        @Override
         public Editor putInt(String key, int value) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key, value);
                 return this;
             }
         }
+        @Override
         public Editor putLong(String key, long value) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key, value);
                 return this;
             }
         }
+        @Override
         public Editor putFloat(String key, float value) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key, value);
                 return this;
             }
         }
+        @Override
         public Editor putBoolean(String key, boolean value) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key, value);
                 return this;
             }
         }
 
+        @Override
         public Editor remove(String key) {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mModified.put(key, this);
                 return this;
             }
         }
 
+        @Override
         public Editor clear() {
-            synchronized (mLock) {
+            synchronized (mEditorLock) {
                 mClear = true;
                 return this;
             }
         }
 
+        @Override
         public void apply() {
             final long startTime = System.currentTimeMillis();
 
             final MemoryCommitResult mcr = commitToMemory();
             final Runnable awaitCommit = new Runnable() {
+                    @Override
                     public void run() {
                         try {
                             mcr.writtenToDiskLatch.await();
@@ -429,6 +448,7 @@
             QueuedWork.addFinisher(awaitCommit);
 
             Runnable postWriteRunnable = new Runnable() {
+                    @Override
                     public void run() {
                         awaitCommit.run();
                         QueuedWork.removeFinisher(awaitCommit);
@@ -471,13 +491,13 @@
                     listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                 }
 
-                synchronized (mLock) {
+                synchronized (mEditorLock) {
                     boolean changesMade = false;
 
                     if (mClear) {
-                        if (!mMap.isEmpty()) {
+                        if (!mapToWriteToDisk.isEmpty()) {
                             changesMade = true;
-                            mMap.clear();
+                            mapToWriteToDisk.clear();
                         }
                         mClear = false;
                     }
@@ -489,18 +509,18 @@
                         // setting a value to "null" for a given key is specified to be
                         // equivalent to calling remove on that key.
                         if (v == this || v == null) {
-                            if (!mMap.containsKey(k)) {
+                            if (!mapToWriteToDisk.containsKey(k)) {
                                 continue;
                             }
-                            mMap.remove(k);
+                            mapToWriteToDisk.remove(k);
                         } else {
-                            if (mMap.containsKey(k)) {
-                                Object existingValue = mMap.get(k);
+                            if (mapToWriteToDisk.containsKey(k)) {
+                                Object existingValue = mapToWriteToDisk.get(k);
                                 if (existingValue != null && existingValue.equals(v)) {
                                     continue;
                                 }
                             }
-                            mMap.put(k, v);
+                            mapToWriteToDisk.put(k, v);
                         }
 
                         changesMade = true;
@@ -522,6 +542,7 @@
                     mapToWriteToDisk);
         }
 
+        @Override
         public boolean commit() {
             long startTime = 0;
 
@@ -564,11 +585,7 @@
                 }
             } else {
                 // Run this function on the main thread.
-                ActivityThread.sMainThreadHandler.post(new Runnable() {
-                        public void run() {
-                            notifyListeners(mcr);
-                        }
-                    });
+                ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
             }
         }
     }
@@ -594,6 +611,7 @@
         final boolean isFromSyncCommit = (postWriteRunnable == null);
 
         final Runnable writeToDiskRunnable = new Runnable() {
+                @Override
                 public void run() {
                     synchronized (mWritingToDiskLock) {
                         writeToFile(mcr, isFromSyncCommit);
@@ -646,7 +664,7 @@
         return str;
     }
 
-    // Note: must hold mWritingToDiskLock
+    @GuardedBy("mWritingToDiskLock")
     private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
         long startTime = 0;
         long existsTime = 0;
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index ffd38e2..f06a925 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -32,29 +32,30 @@
 public final class ConnectEvent extends NetworkEvent implements Parcelable {
 
     /** The destination IP address. */
-    private final String ipAddress;
+    private final String mIpAddress;
 
     /** The destination port number. */
-    private final int port;
+    private final int mPort;
 
     /** @hide */
     public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) {
         super(packageName, timestamp);
-        this.ipAddress = ipAddress;
-        this.port = port;
+        this.mIpAddress = ipAddress;
+        this.mPort = port;
     }
 
     private ConnectEvent(Parcel in) {
-        this.ipAddress = in.readString();
-        this.port = in.readInt();
-        this.packageName = in.readString();
-        this.timestamp = in.readLong();
+        this.mIpAddress = in.readString();
+        this.mPort = in.readInt();
+        this.mPackageName = in.readString();
+        this.mTimestamp = in.readLong();
+        this.mId = in.readLong();
     }
 
     public InetAddress getInetAddress() {
         try {
             // ipAddress is already an address, not a host name, no DNS resolution will happen.
-            return InetAddress.getByName(ipAddress);
+            return InetAddress.getByName(mIpAddress);
         } catch (UnknownHostException e) {
             // Should never happen as we aren't passing a host name.
             return InetAddress.getLoopbackAddress();
@@ -62,13 +63,13 @@
     }
 
     public int getPort() {
-        return port;
+        return mPort;
     }
 
     @Override
     public String toString() {
-        return String.format("ConnectEvent(%s, %d, %d, %s)", ipAddress, port, timestamp,
-                packageName);
+        return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+                mPackageName);
     }
 
     public static final Parcelable.Creator<ConnectEvent> CREATOR
@@ -96,10 +97,10 @@
     public void writeToParcel(Parcel out, int flags) {
         // write parcel token first
         out.writeInt(PARCEL_TOKEN_CONNECT_EVENT);
-        out.writeString(ipAddress);
-        out.writeInt(port);
-        out.writeString(packageName);
-        out.writeLong(timestamp);
+        out.writeString(mIpAddress);
+        out.writeInt(mPort);
+        out.writeString(mPackageName);
+        out.writeLong(mTimestamp);
+        out.writeLong(mId);
     }
 }
-
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index f84c5b0..4ddf13e 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -34,46 +34,47 @@
 public final class DnsEvent extends NetworkEvent implements Parcelable {
 
     /** The hostname that was looked up. */
-    private final String hostname;
+    private final String mHostname;
 
     /** Contains (possibly a subset of) the IP addresses returned. */
-    private final String[] ipAddresses;
+    private final String[] mIpAddresses;
 
     /**
      * The number of IP addresses returned from the DNS lookup event. May be different from the
      * length of ipAddresses if there were too many addresses to log.
      */
-    private final int ipAddressesCount;
+    private final int mIpAddressesCount;
 
     /** @hide */
     public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
             String packageName, long timestamp) {
         super(packageName, timestamp);
-        this.hostname = hostname;
-        this.ipAddresses = ipAddresses;
-        this.ipAddressesCount = ipAddressesCount;
+        this.mHostname = hostname;
+        this.mIpAddresses = ipAddresses;
+        this.mIpAddressesCount = ipAddressesCount;
     }
 
     private DnsEvent(Parcel in) {
-        this.hostname = in.readString();
-        this.ipAddresses = in.createStringArray();
-        this.ipAddressesCount = in.readInt();
-        this.packageName = in.readString();
-        this.timestamp = in.readLong();
+        this.mHostname = in.readString();
+        this.mIpAddresses = in.createStringArray();
+        this.mIpAddressesCount = in.readInt();
+        this.mPackageName = in.readString();
+        this.mTimestamp = in.readLong();
+        this.mId = in.readLong();
     }
 
     /** Returns the hostname that was looked up. */
     public String getHostname() {
-        return hostname;
+        return mHostname;
     }
 
     /** Returns (possibly a subset of) the IP addresses returned. */
     public List<InetAddress> getInetAddresses() {
-        if (ipAddresses == null || ipAddresses.length == 0) {
+        if (mIpAddresses == null || mIpAddresses.length == 0) {
             return Collections.emptyList();
         }
-        final List<InetAddress> inetAddresses = new ArrayList<>(ipAddresses.length);
-        for (final String ipAddress : ipAddresses) {
+        final List<InetAddress> inetAddresses = new ArrayList<>(mIpAddresses.length);
+        for (final String ipAddress : mIpAddresses) {
             try {
                 // ipAddress is already an address, not a host name, no DNS resolution will happen.
                 inetAddresses.add(InetAddress.getByName(ipAddress));
@@ -90,14 +91,14 @@
      * addresses to log.
      */
     public int getTotalResolvedAddressCount() {
-        return ipAddressesCount;
+        return mIpAddressesCount;
     }
 
     @Override
     public String toString() {
-        return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname,
-                (ipAddresses == null) ? "NONE" : String.join(" ", ipAddresses),
-                ipAddressesCount, timestamp, packageName);
+        return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+                (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
+                mIpAddressesCount, mTimestamp, mPackageName);
     }
 
     public static final Parcelable.Creator<DnsEvent> CREATOR
@@ -125,11 +126,11 @@
     public void writeToParcel(Parcel out, int flags) {
         // write parcel token first
         out.writeInt(PARCEL_TOKEN_DNS_EVENT);
-        out.writeString(hostname);
-        out.writeStringArray(ipAddresses);
-        out.writeInt(ipAddressesCount);
-        out.writeString(packageName);
-        out.writeLong(timestamp);
+        out.writeString(mHostname);
+        out.writeStringArray(mIpAddresses);
+        out.writeInt(mIpAddressesCount);
+        out.writeString(mPackageName);
+        out.writeLong(mTimestamp);
+        out.writeLong(mId);
     }
 }
-
diff --git a/core/java/android/app/admin/NetworkEvent.java b/core/java/android/app/admin/NetworkEvent.java
index 2646c3f..947e4fe 100644
--- a/core/java/android/app/admin/NetworkEvent.java
+++ b/core/java/android/app/admin/NetworkEvent.java
@@ -18,8 +18,8 @@
 
 import android.content.pm.PackageManager;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.ParcelFormatException;
+import android.os.Parcelable;
 
 /**
  * An abstract class that represents a network event.
@@ -32,10 +32,13 @@
     static final int PARCEL_TOKEN_CONNECT_EVENT = 2;
 
     /** The package name of the UID that performed the query. */
-    String packageName;
+    String mPackageName;
 
     /** The timestamp of the event being reported in milliseconds. */
-    long timestamp;
+    long mTimestamp;
+
+    /** The id of the event. */
+    long mId;
 
     /** @hide */
     NetworkEvent() {
@@ -44,8 +47,8 @@
 
     /** @hide */
     NetworkEvent(String packageName, long timestamp) {
-        this.packageName = packageName;
-        this.timestamp = timestamp;
+        this.mPackageName = packageName;
+        this.mTimestamp = timestamp;
     }
 
     /**
@@ -53,7 +56,7 @@
      * {@link PackageManager#getNameForUid}.
      */
     public String getPackageName() {
-        return packageName;
+        return mPackageName;
     }
 
     /**
@@ -61,7 +64,20 @@
      * the time the event was reported and midnight, January 1, 1970 UTC.
      */
     public long getTimestamp() {
-        return timestamp;
+        return mTimestamp;
+    }
+
+    /** @hide */
+    public void setId(long id) {
+        this.mId = id;
+    }
+
+    /**
+     * Returns the id of the event, where the id monotonically increases for each event. The id
+     * is reset when the device reboots, and when network logging is enabled.
+     */
+    public long getId() {
+        return this.mId;
     }
 
     @Override
@@ -95,4 +111,3 @@
     @Override
     public abstract void writeToParcel(Parcel out, int flags);
 }
-
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
new file mode 100644
index 0000000..07001e2
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity configuration changed callback.
+ * @hide
+ */
+public class ActivityConfigurationChangeItem extends ClientTransactionItem {
+
+    private final Configuration mConfiguration;
+
+    public ActivityConfigurationChangeItem(Configuration configuration) {
+        mConfiguration = configuration;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
+        client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedObject(mConfiguration, flags);
+    }
+
+    /** Read from Parcel. */
+    private ActivityConfigurationChangeItem(Parcel in) {
+        mConfiguration = in.readTypedObject(Configuration.CREATOR);
+    }
+
+    public static final Creator<ActivityConfigurationChangeItem> CREATOR =
+            new Creator<ActivityConfigurationChangeItem>() {
+        public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
+            return new ActivityConfigurationChangeItem(in);
+        }
+
+        public ActivityConfigurationChangeItem[] newArray(int size) {
+            return new ActivityConfigurationChangeItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
+        return mConfiguration.equals(other.mConfiguration);
+    }
+
+    @Override
+    public int hashCode() {
+        return mConfiguration.hashCode();
+    }
+}
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
new file mode 100644
index 0000000..a64108d
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Request for lifecycle state that an activity should reach.
+ * @hide
+ */
+public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+
+    static final boolean DEBUG_ORDER = false;
+
+    @IntDef({UNDEFINED, RESUMED, PAUSED, STOPPED, DESTROYED})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface LifecycleState{}
+    public static final int UNDEFINED = -1;
+    public static final int RESUMED = 0;
+    public static final int PAUSED = 1;
+    public static final int STOPPED = 2;
+    public static final int DESTROYED = 3;
+
+    /** A final lifecycle state that an activity should reach. */
+    @LifecycleState
+    public abstract int getTargetState();
+}
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
new file mode 100644
index 0000000..76664d8
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ResultInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import java.util.List;
+
+/**
+ * Activity result delivery callback.
+ * @hide
+ */
+public class ActivityResultItem extends ClientTransactionItem {
+
+    private final List<ResultInfo> mResultInfoList;
+
+    public ActivityResultItem(List<ResultInfo> resultInfos) {
+        mResultInfoList = resultInfos;
+    }
+
+    @Override
+    public int getPreExecutionState() {
+        return PAUSED;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
+        client.handleSendResult(token, mResultInfoList);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedList(mResultInfoList, flags);
+    }
+
+    /** Read from Parcel. */
+    private ActivityResultItem(Parcel in) {
+        mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
+    }
+
+    public static final Parcelable.Creator<ActivityResultItem> CREATOR =
+            new Parcelable.Creator<ActivityResultItem>() {
+        public ActivityResultItem createFromParcel(Parcel in) {
+            return new ActivityResultItem(in);
+        }
+
+        public ActivityResultItem[] newArray(int size) {
+            return new ActivityResultItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ActivityResultItem other = (ActivityResultItem) o;
+        return mResultInfoList.equals(other.mResultInfoList);
+    }
+
+    @Override
+    public int hashCode() {
+        return mResultInfoList.hashCode();
+    }
+}
diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java
new file mode 100644
index 0000000..4bd01af
--- /dev/null
+++ b/core/java/android/app/servertransaction/BaseClientRequest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+/**
+ * Base interface for individual requests from server to client.
+ * Each of them can be prepared before scheduling and, eventually, executed.
+ * @hide
+ */
+public interface BaseClientRequest {
+
+    /**
+     * Prepare the client request before scheduling.
+     * An example of this might be informing about pending updates for some values.
+     *
+     * @param client Target client handler.
+     * @param token  Target activity token.
+     */
+    default void prepare(ClientTransactionHandler client, IBinder token) {
+    }
+
+    /**
+     * Execute the request.
+     * @param client Target client handler.
+     * @param token Target activity token.
+     */
+    void execute(ClientTransactionHandler client, IBinder token);
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.aidl b/core/java/android/app/servertransaction/ClientTransaction.aidl
new file mode 100644
index 0000000..ad8bcbf
--- /dev/null
+++ b/core/java/android/app/servertransaction/ClientTransaction.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+/** @hide */
+parcelable ClientTransaction;
\ No newline at end of file
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
new file mode 100644
index 0000000..d2289ba
--- /dev/null
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.app.IApplicationThread;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A container that holds a sequence of messages, which may be sent to a client.
+ * This includes a list of callbacks and a final lifecycle state.
+ *
+ * @see com.android.server.am.ClientLifecycleManager
+ * @see ClientTransactionItem
+ * @see ActivityLifecycleItem
+ * @hide
+ */
+public class ClientTransaction implements Parcelable {
+
+    /** A list of individual callbacks to a client. */
+    private List<ClientTransactionItem> mActivityCallbacks;
+
+    /**
+     * Final lifecycle state in which the client activity should be after the transaction is
+     * executed.
+     */
+    private ActivityLifecycleItem mLifecycleStateRequest;
+
+    /** Target client. */
+    private IApplicationThread mClient;
+
+    /** Target client activity. Might be null if the entire transaction is targeting an app. */
+    private IBinder mActivityToken;
+
+    public ClientTransaction(IApplicationThread client, IBinder activityToken) {
+        mClient = client;
+        mActivityToken = activityToken;
+    }
+
+    /**
+     * Add a message to the end of the sequence of callbacks.
+     * @param activityCallback A single message that can contain a lifecycle request/callback.
+     */
+    public void addCallback(ClientTransactionItem activityCallback) {
+        if (mActivityCallbacks == null) {
+            mActivityCallbacks = new ArrayList<>();
+        }
+        mActivityCallbacks.add(activityCallback);
+    }
+
+    /**
+     * Set the lifecycle state in which the client should be after executing the transaction.
+     * @param stateRequest A lifecycle request initialized with right parameters.
+     */
+    public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) {
+        mLifecycleStateRequest = stateRequest;
+    }
+
+    /**
+     * Do what needs to be done while the transaction is being scheduled on the client side.
+     * @param clientTransactionHandler Handler on the client side that will executed all operations
+     *                                 requested by transaction items.
+     */
+    public void prepare(android.app.ClientTransactionHandler clientTransactionHandler) {
+        if (mActivityCallbacks != null) {
+            final int size = mActivityCallbacks.size();
+            for (int i = 0; i < size; ++i) {
+                mActivityCallbacks.get(i).prepare(clientTransactionHandler, mActivityToken);
+            }
+        }
+        if (mLifecycleStateRequest != null) {
+            mLifecycleStateRequest.prepare(clientTransactionHandler, mActivityToken);
+        }
+    }
+
+    /**
+     * Execute the transaction.
+     * @param clientTransactionHandler Handler on the client side that will execute all operations
+     *                                 requested by transaction items.
+     */
+    public void execute(android.app.ClientTransactionHandler clientTransactionHandler) {
+        if (mActivityCallbacks != null) {
+            final int size = mActivityCallbacks.size();
+            for (int i = 0; i < size; ++i) {
+                mActivityCallbacks.get(i).execute(clientTransactionHandler, mActivityToken);
+            }
+        }
+        if (mLifecycleStateRequest != null) {
+            mLifecycleStateRequest.execute(clientTransactionHandler, mActivityToken);
+        }
+    }
+
+    /**
+     * Schedule the transaction after it was initialized. It will be send to client and all its
+     * individual parts will be applied in the following sequence:
+     * 1. The client calls {@link #prepare(ClientTransactionHandler)}, which triggers all work that
+     *    needs to be done before actually scheduling the transaction for callbacks and lifecycle
+     *    state request.
+     * 2. The transaction message is scheduled.
+     * 3. The client calls {@link #execute(ClientTransactionHandler)}, which executes all callbacks
+     *    and necessary lifecycle transitions.
+     */
+    public void schedule() throws RemoteException {
+        mClient.scheduleTransaction(this);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mClient.asBinder());
+        final boolean writeActivityToken = mActivityToken != null;
+        dest.writeBoolean(writeActivityToken);
+        if (writeActivityToken) {
+            dest.writeStrongBinder(mActivityToken);
+        }
+        dest.writeParcelable(mLifecycleStateRequest, flags);
+        final boolean writeActivityCallbacks = mActivityCallbacks != null;
+        dest.writeBoolean(writeActivityCallbacks);
+        if (writeActivityCallbacks) {
+            dest.writeParcelableList(mActivityCallbacks, flags);
+        }
+    }
+
+    /** Read from Parcel. */
+    private ClientTransaction(Parcel in) {
+        mClient = (IApplicationThread) in.readStrongBinder();
+        final boolean readActivityToken = in.readBoolean();
+        if (readActivityToken) {
+            mActivityToken = in.readStrongBinder();
+        }
+        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
+        final boolean readActivityCallbacks = in.readBoolean();
+        if (readActivityCallbacks) {
+            mActivityCallbacks = new ArrayList<>();
+            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
+        }
+    }
+
+    public static final Creator<ClientTransaction> CREATOR =
+            new Creator<ClientTransaction>() {
+        public ClientTransaction createFromParcel(Parcel in) {
+            return new ClientTransaction(in);
+        }
+
+        public ClientTransaction[] newArray(int size) {
+            return new ClientTransaction[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ClientTransaction other = (ClientTransaction) o;
+        return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+                && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
+                && mClient == other.mClient
+                && mActivityToken == other.mActivityToken;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mActivityCallbacks);
+        result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
new file mode 100644
index 0000000..6f2cc00
--- /dev/null
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+
+import android.os.Parcelable;
+
+/**
+ * A callback message to a client that can be scheduled and executed.
+ * Examples of these might be activity configuration change, multi-window mode change, activity
+ * result delivery etc.
+ *
+ * @see ClientTransaction
+ * @see com.android.server.am.ClientLifecycleManager
+ * @hide
+ */
+public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable {
+
+    /** Get the state in which this callback can be executed. */
+    @LifecycleState
+    public int getPreExecutionState() {
+        return UNDEFINED;
+    }
+
+    /** Get the state that must follow this callback. */
+    @LifecycleState
+    public int getPostExecutionState() {
+        return UNDEFINED;
+    }
+
+
+    // Parcelable
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
new file mode 100644
index 0000000..055923e
--- /dev/null
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * App configuration change message.
+ * @hide
+ */
+public class ConfigurationChangeItem extends ClientTransactionItem {
+
+    private final Configuration mConfiguration;
+
+    public ConfigurationChangeItem(Configuration configuration) {
+        mConfiguration = new Configuration(configuration);
+    }
+
+    @Override
+    public void prepare(android.app.ClientTransactionHandler client, IBinder token) {
+        client.updatePendingConfiguration(mConfiguration);
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        client.handleConfigurationChanged(mConfiguration);
+    }
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedObject(mConfiguration, flags);
+    }
+
+    /** Read from Parcel. */
+    private ConfigurationChangeItem(Parcel in) {
+        mConfiguration = in.readTypedObject(Configuration.CREATOR);
+    }
+
+    public static final Creator<ConfigurationChangeItem> CREATOR =
+            new Creator<ConfigurationChangeItem>() {
+        public ConfigurationChangeItem createFromParcel(Parcel in) {
+            return new ConfigurationChangeItem(in);
+        }
+
+        public ConfigurationChangeItem[] newArray(int size) {
+            return new ConfigurationChangeItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
+        return mConfiguration.equals(other.mConfiguration);
+    }
+
+    @Override
+    public int hashCode() {
+        return mConfiguration.hashCode();
+    }
+}
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
new file mode 100644
index 0000000..38fd5fb
--- /dev/null
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to destroy an activity.
+ * @hide
+ */
+public class DestroyActivityItem extends ActivityLifecycleItem {
+
+    private final boolean mFinished;
+    private final int mConfigChanges;
+
+    public DestroyActivityItem(boolean finished, int configChanges) {
+        mFinished = finished;
+        mConfigChanges = configChanges;
+    }
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
+        client.handleDestroyActivity(token, mFinished, mConfigChanges,
+                false /* getNonConfigInstance */);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return DESTROYED;
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mFinished);
+        dest.writeInt(mConfigChanges);
+    }
+
+    /** Read from Parcel. */
+    private DestroyActivityItem(Parcel in) {
+        mFinished = in.readBoolean();
+        mConfigChanges = in.readInt();
+    }
+
+    public static final Creator<DestroyActivityItem> CREATOR =
+            new Creator<DestroyActivityItem>() {
+        public DestroyActivityItem createFromParcel(Parcel in) {
+            return new DestroyActivityItem(in);
+        }
+
+        public DestroyActivityItem[] newArray(int size) {
+            return new DestroyActivityItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final DestroyActivityItem other = (DestroyActivityItem) o;
+        return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mFinished ? 1 : 0);
+        result = 31 * result + mConfigChanges;
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
new file mode 100644
index 0000000..417ebac
--- /dev/null
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.Trace;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request to launch an activity.
+ * @hide
+ */
+public class LaunchActivityItem extends ActivityLifecycleItem {
+
+    private final Intent mIntent;
+    private final int mIdent;
+    private final ActivityInfo mInfo;
+    private final Configuration mCurConfig;
+    private final Configuration mOverrideConfig;
+    private final CompatibilityInfo mCompatInfo;
+    private final String mReferrer;
+    private final IVoiceInteractor mVoiceInteractor;
+    private final int mProcState;
+    private final Bundle mState;
+    private final PersistableBundle mPersistentState;
+    private final List<ResultInfo> mPendingResults;
+    private final List<ReferrerIntent> mPendingNewIntents;
+    // TODO(lifecycler): use lifecycle request instead of this param.
+    private final boolean mNotResumed;
+    private final boolean mIsForward;
+    private final ProfilerInfo mProfilerInfo;
+
+    public LaunchActivityItem(Intent intent, int ident, ActivityInfo info,
+            Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
+            String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
+            PersistableBundle persistentState, List<ResultInfo> pendingResults,
+            List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+            ProfilerInfo profilerInfo) {
+        mIntent = intent;
+        mIdent = ident;
+        mInfo = info;
+        mCurConfig = curConfig;
+        mOverrideConfig = overrideConfig;
+        mCompatInfo = compatInfo;
+        mReferrer = referrer;
+        mVoiceInteractor = voiceInteractor;
+        mProcState = procState;
+        mState = state;
+        mPersistentState = persistentState;
+        mPendingResults = pendingResults;
+        mPendingNewIntents = pendingNewIntents;
+        mNotResumed = notResumed;
+        mIsForward = isForward;
+        mProfilerInfo = profilerInfo;
+    }
+
+    @Override
+    public void prepare(ClientTransactionHandler client, IBinder token) {
+        client.updateProcessState(mProcState, false);
+        client.updatePendingConfiguration(mCurConfig);
+    }
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
+        client.handleLaunchActivity(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo,
+                mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults,
+                mPendingNewIntents, mNotResumed, mIsForward, mProfilerInfo);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return mNotResumed ? PAUSED : RESUMED;
+    }
+
+
+    // Parcelable implementation
+
+    /** Write from Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedObject(mIntent, flags);
+        dest.writeInt(mIdent);
+        dest.writeTypedObject(mInfo, flags);
+        dest.writeTypedObject(mCurConfig, flags);
+        dest.writeTypedObject(mOverrideConfig, flags);
+        dest.writeTypedObject(mCompatInfo, flags);
+        dest.writeString(mReferrer);
+        dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null);
+        dest.writeInt(mProcState);
+        dest.writeBundle(mState);
+        dest.writePersistableBundle(mPersistentState);
+        dest.writeTypedList(mPendingResults, flags);
+        dest.writeTypedList(mPendingNewIntents, flags);
+        dest.writeBoolean(mNotResumed);
+        dest.writeBoolean(mIsForward);
+        dest.writeTypedObject(mProfilerInfo, flags);
+    }
+
+    /** Read from Parcel. */
+    private LaunchActivityItem(Parcel in) {
+        mIntent = in.readTypedObject(Intent.CREATOR);
+        mIdent = in.readInt();
+        mInfo = in.readTypedObject(ActivityInfo.CREATOR);
+        mCurConfig = in.readTypedObject(Configuration.CREATOR);
+        mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+        mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR);
+        mReferrer = in.readString();
+        mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder();
+        mProcState = in.readInt();
+        mState = in.readBundle(getClass().getClassLoader());
+        mPersistentState = in.readPersistableBundle(getClass().getClassLoader());
+        mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
+        mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+        mNotResumed = in.readBoolean();
+        mIsForward = in.readBoolean();
+        mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR);
+    }
+
+    public static final Creator<LaunchActivityItem> CREATOR =
+            new Creator<LaunchActivityItem>() {
+        public LaunchActivityItem createFromParcel(Parcel in) {
+            return new LaunchActivityItem(in);
+        }
+
+        public LaunchActivityItem[] newArray(int size) {
+            return new LaunchActivityItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final LaunchActivityItem other = (LaunchActivityItem) o;
+        return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent
+                && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
+                && Objects.equals(mOverrideConfig, other.mOverrideConfig)
+                && Objects.equals(mCompatInfo, other.mCompatInfo)
+                && Objects.equals(mReferrer, other.mReferrer)
+                && mProcState == other.mProcState && areBundlesEqual(mState, other.mState)
+                && areBundlesEqual(mPersistentState, other.mPersistentState)
+                && Objects.equals(mPendingResults, other.mPendingResults)
+                && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
+                && mNotResumed == other.mNotResumed && mIsForward == other.mIsForward
+                && Objects.equals(mProfilerInfo, other.mProfilerInfo);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mIntent.filterHashCode();
+        result = 31 * result + mIdent;
+        result = 31 * result + Objects.hashCode(mCurConfig);
+        result = 31 * result + Objects.hashCode(mOverrideConfig);
+        result = 31 * result + Objects.hashCode(mCompatInfo);
+        result = 31 * result + Objects.hashCode(mReferrer);
+        result = 31 * result + Objects.hashCode(mProcState);
+        result = 31 * result + (mState != null ? mState.size() : 0);
+        result = 31 * result + (mPersistentState != null ? mPersistentState.size() : 0);
+        result = 31 * result + Objects.hashCode(mPendingResults);
+        result = 31 * result + Objects.hashCode(mPendingNewIntents);
+        result = 31 * result + (mNotResumed ? 1 : 0);
+        result = 31 * result + (mIsForward ? 1 : 0);
+        result = 31 * result + Objects.hashCode(mProfilerInfo);
+        return result;
+    }
+
+    private boolean activityInfoEqual(ActivityInfo other) {
+        return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio
+                && Objects.equals(mInfo.launchToken, other.launchToken)
+                && Objects.equals(mInfo.getComponentName(), other.getComponentName());
+    }
+
+    private static boolean areBundlesEqual(BaseBundle extras, BaseBundle newExtras) {
+        if (extras == null || newExtras == null) {
+            return extras == newExtras;
+        }
+
+        if (extras.size() != newExtras.size()) {
+            return false;
+        }
+
+        for (String key : extras.keySet()) {
+            if (key != null) {
+                final Object value = extras.get(key);
+                final Object newValue = newExtras.get(key);
+                if (!Objects.equals(value, newValue)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
new file mode 100644
index 0000000..ccd80d8
--- /dev/null
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity move to a different display message.
+ * @hide
+ */
+public class MoveToDisplayItem extends ClientTransactionItem {
+
+    private final int mTargetDisplayId;
+    private final Configuration mConfiguration;
+
+    public MoveToDisplayItem(int targetDisplayId, Configuration configuration) {
+        mTargetDisplayId = targetDisplayId;
+        mConfiguration = configuration;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
+        client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mTargetDisplayId);
+        dest.writeTypedObject(mConfiguration, flags);
+    }
+
+    /** Read from Parcel. */
+    private MoveToDisplayItem(Parcel in) {
+        mTargetDisplayId = in.readInt();
+        mConfiguration = in.readTypedObject(Configuration.CREATOR);
+    }
+
+    public static final Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
+        public MoveToDisplayItem createFromParcel(Parcel in) {
+            return new MoveToDisplayItem(in);
+        }
+
+        public MoveToDisplayItem[] newArray(int size) {
+            return new MoveToDisplayItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final MoveToDisplayItem other = (MoveToDisplayItem) o;
+        return mTargetDisplayId == other.mTargetDisplayId
+                && mConfiguration.equals(other.mConfiguration);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mTargetDisplayId;
+        result = 31 * result + mConfiguration.hashCode();
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
new file mode 100644
index 0000000..a0c617f
--- /dev/null
+++ b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Multi-window mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class MultiWindowModeChangeItem extends ClientTransactionItem {
+
+    private final boolean mIsInMultiWindowMode;
+    private final Configuration mOverrideConfig;
+
+    public MultiWindowModeChangeItem(boolean isInMultiWindowMode,
+            Configuration overrideConfig) {
+        mIsInMultiWindowMode = isInMultiWindowMode;
+        mOverrideConfig = overrideConfig;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mIsInMultiWindowMode);
+        dest.writeTypedObject(mOverrideConfig, flags);
+    }
+
+    /** Read from Parcel. */
+    private MultiWindowModeChangeItem(Parcel in) {
+        mIsInMultiWindowMode = in.readBoolean();
+        mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+    }
+
+    public static final Creator<MultiWindowModeChangeItem> CREATOR =
+            new Creator<MultiWindowModeChangeItem>() {
+        public MultiWindowModeChangeItem createFromParcel(Parcel in) {
+            return new MultiWindowModeChangeItem(in);
+        }
+
+        public MultiWindowModeChangeItem[] newArray(int size) {
+            return new MultiWindowModeChangeItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o;
+        return mIsInMultiWindowMode == other.mIsInMultiWindowMode
+                && mOverrideConfig.equals(other.mOverrideConfig);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mIsInMultiWindowMode ? 1 : 0);
+        result = 31 * result + mOverrideConfig.hashCode();
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
new file mode 100644
index 0000000..61a8965
--- /dev/null
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.app.servertransaction.ActivityLifecycleItem.RESUMED;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * New intent message.
+ * @hide
+ */
+public class NewIntentItem extends ClientTransactionItem {
+
+    private final List<ReferrerIntent> mIntents;
+    private final boolean mPause;
+
+    public NewIntentItem(List<ReferrerIntent> intents, boolean pause) {
+        mIntents = intents;
+        mPause = pause;
+    }
+
+    @Override
+    public int getPreExecutionState() {
+        return PAUSED;
+    }
+
+    @Override
+    public int getPostExecutionState() {
+        return RESUMED;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
+        client.handleNewIntent(token, mIntents, mPause);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mPause);
+        dest.writeTypedList(mIntents, flags);
+    }
+
+    /** Read from Parcel. */
+    private NewIntentItem(Parcel in) {
+        mPause = in.readBoolean();
+        mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+    }
+
+    public static final Parcelable.Creator<NewIntentItem> CREATOR =
+            new Parcelable.Creator<NewIntentItem>() {
+        public NewIntentItem createFromParcel(Parcel in) {
+            return new NewIntentItem(in);
+        }
+
+        public NewIntentItem[] newArray(int size) {
+            return new NewIntentItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final NewIntentItem other = (NewIntentItem) o;
+        return mPause == other.mPause && mIntents.equals(other.mIntents);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mPause ? 1 : 0);
+        result = 31 * result + mIntents.hashCode();
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
new file mode 100644
index 0000000..e561a4b
--- /dev/null
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to paused state.
+ * @hide
+ */
+public class PauseActivityItem extends ActivityLifecycleItem {
+
+    private static final String TAG = "PauseActivityItem";
+
+    private final boolean mFinished;
+    private final boolean mUserLeaving;
+    private final int mConfigChanges;
+    private final boolean mDontReport;
+
+    private int mLifecycleSeq;
+
+    public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges,
+            boolean dontReport) {
+        mFinished = finished;
+        mUserLeaving = userLeaving;
+        mConfigChanges = configChanges;
+        mDontReport = dontReport;
+    }
+
+    @Override
+    public void prepare(ClientTransactionHandler client, IBinder token) {
+        mLifecycleSeq = client.getLifecycleSeq();
+        if (DEBUG_ORDER) {
+            Slog.d(TAG, "Pause transaction for " + client + " received seq: "
+                    + mLifecycleSeq);
+        }
+    }
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
+        client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport,
+                mLifecycleSeq);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return PAUSED;
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mFinished);
+        dest.writeBoolean(mUserLeaving);
+        dest.writeInt(mConfigChanges);
+        dest.writeBoolean(mDontReport);
+    }
+
+    /** Read from Parcel. */
+    private PauseActivityItem(Parcel in) {
+        mFinished = in.readBoolean();
+        mUserLeaving = in.readBoolean();
+        mConfigChanges = in.readInt();
+        mDontReport = in.readBoolean();
+    }
+
+    public static final Creator<PauseActivityItem> CREATOR =
+            new Creator<PauseActivityItem>() {
+        public PauseActivityItem createFromParcel(Parcel in) {
+            return new PauseActivityItem(in);
+        }
+
+        public PauseActivityItem[] newArray(int size) {
+            return new PauseActivityItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final PauseActivityItem other = (PauseActivityItem) o;
+        return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
+                && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mFinished ? 1 : 0);
+        result = 31 * result + (mUserLeaving ? 1 : 0);
+        result = 31 * result + mConfigChanges;
+        result = 31 * result + (mDontReport ? 1 : 0);
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/PipModeChangeItem.java b/core/java/android/app/servertransaction/PipModeChangeItem.java
new file mode 100644
index 0000000..923839e
--- /dev/null
+++ b/core/java/android/app/servertransaction/PipModeChangeItem.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Picture in picture mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class PipModeChangeItem extends ClientTransactionItem {
+
+    private final boolean mIsInPipMode;
+    private final Configuration mOverrideConfig;
+
+    public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) {
+        mIsInPipMode = isInPipMode;
+        mOverrideConfig = overrideConfig;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mIsInPipMode);
+        dest.writeTypedObject(mOverrideConfig, flags);
+    }
+
+    /** Read from Parcel. */
+    private PipModeChangeItem(Parcel in) {
+        mIsInPipMode = in.readBoolean();
+        mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+    }
+
+    public static final Creator<PipModeChangeItem> CREATOR =
+            new Creator<PipModeChangeItem>() {
+        public PipModeChangeItem createFromParcel(Parcel in) {
+            return new PipModeChangeItem(in);
+        }
+
+        public PipModeChangeItem[] newArray(int size) {
+            return new PipModeChangeItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final PipModeChangeItem other = (PipModeChangeItem) o;
+        return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mIsInPipMode ? 1 : 0);
+        result = 31 * result + mOverrideConfig.hashCode();
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
new file mode 100644
index 0000000..ea31a46
--- /dev/null
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to resumed state.
+ * @hide
+ */
+public class ResumeActivityItem extends ActivityLifecycleItem {
+
+    private static final String TAG = "ResumeActivityItem";
+
+    private final int mProcState;
+    private final boolean mIsForward;
+
+    private int mLifecycleSeq;
+
+    public ResumeActivityItem(int procState, boolean isForward) {
+        mProcState = procState;
+        mIsForward = isForward;
+    }
+
+    @Override
+    public void prepare(ClientTransactionHandler client, IBinder token) {
+        mLifecycleSeq = client.getLifecycleSeq();
+        if (DEBUG_ORDER) {
+            Slog.d(TAG, "Resume transaction for " + client + " received seq: "
+                    + mLifecycleSeq);
+        }
+        client.updateProcessState(mProcState, false);
+    }
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
+        client.handleResumeActivity(token, true /* clearHide */, mIsForward,
+                true /* reallyResume */, mLifecycleSeq, "RESUME_ACTIVITY");
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return RESUMED;
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mProcState);
+        dest.writeBoolean(mIsForward);
+    }
+
+    /** Read from Parcel. */
+    private ResumeActivityItem(Parcel in) {
+        mProcState = in.readInt();
+        mIsForward = in.readBoolean();
+    }
+
+    public static final Creator<ResumeActivityItem> CREATOR =
+            new Creator<ResumeActivityItem>() {
+        public ResumeActivityItem createFromParcel(Parcel in) {
+            return new ResumeActivityItem(in);
+        }
+
+        public ResumeActivityItem[] newArray(int size) {
+            return new ResumeActivityItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ResumeActivityItem other = (ResumeActivityItem) o;
+        return mProcState == other.mProcState && mIsForward == other.mIsForward;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mProcState;
+        result = 31 * result + (mIsForward ? 1 : 0);
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
new file mode 100644
index 0000000..d62c507
--- /dev/null
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to stopped state.
+ * @hide
+ */
+public class StopActivityItem extends ActivityLifecycleItem {
+
+    private static final String TAG = "StopActivityItem";
+
+    private final boolean mShowWindow;
+    private final int mConfigChanges;
+
+    private int mLifecycleSeq;
+
+    public StopActivityItem(boolean showWindow, int configChanges) {
+        mShowWindow = showWindow;
+        mConfigChanges = configChanges;
+    }
+
+    @Override
+    public void prepare(ClientTransactionHandler client, IBinder token) {
+        mLifecycleSeq = client.getLifecycleSeq();
+        if (DEBUG_ORDER) {
+            Slog.d(TAG, "Stop transaction for " + client + " received seq: "
+                    + mLifecycleSeq);
+        }
+    }
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
+        client.handleStopActivity(token, mShowWindow, mConfigChanges, mLifecycleSeq);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return STOPPED;
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mShowWindow);
+        dest.writeInt(mConfigChanges);
+    }
+
+    /** Read from Parcel. */
+    private StopActivityItem(Parcel in) {
+        mShowWindow = in.readBoolean();
+        mConfigChanges = in.readInt();
+    }
+
+    public static final Creator<StopActivityItem> CREATOR =
+            new Creator<StopActivityItem>() {
+        public StopActivityItem createFromParcel(Parcel in) {
+            return new StopActivityItem(in);
+        }
+
+        public StopActivityItem[] newArray(int size) {
+            return new StopActivityItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final StopActivityItem other = (StopActivityItem) o;
+        return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mShowWindow ? 1 : 0);
+        result = 31 * result + mConfigChanges;
+        return result;
+    }
+}
diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java
new file mode 100644
index 0000000..8e88b38
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowVisibilityItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Window visibility change message.
+ * @hide
+ */
+public class WindowVisibilityItem extends ClientTransactionItem {
+
+    private final boolean mShowWindow;
+
+    public WindowVisibilityItem(boolean showWindow) {
+        mShowWindow = showWindow;
+    }
+
+    @Override
+    public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
+        client.handleWindowVisibility(token, mShowWindow);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mShowWindow);
+    }
+
+    /** Read from Parcel. */
+    private WindowVisibilityItem(Parcel in) {
+        mShowWindow = in.readBoolean();
+    }
+
+    public static final Creator<WindowVisibilityItem> CREATOR =
+            new Creator<WindowVisibilityItem>() {
+        public WindowVisibilityItem createFromParcel(Parcel in) {
+            return new WindowVisibilityItem(in);
+        }
+
+        public WindowVisibilityItem[] newArray(int size) {
+            return new WindowVisibilityItem[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowVisibilityItem other = (WindowVisibilityItem) o;
+        return mShowWindow == other.mShowWindow;
+    }
+
+    @Override
+    public int hashCode() {
+        return 17 + 31 * (mShowWindow ? 1 : 0);
+    }
+}
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index 8d81199..cdeee35 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -216,7 +216,7 @@
      * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
      */
     public Slice getSlice() {
-        if (getFormat() == FORMAT_ACTION) {
+        if (FORMAT_ACTION.equals(getFormat())) {
             return ((Pair<PendingIntent, Slice>) mObj).second;
         }
         return (Slice) mObj;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 837c00a..f8cdce6 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1156,6 +1156,7 @@
         dest.writeString(permission);
         dest.writeString(taskAffinity);
         dest.writeString(targetActivity);
+        dest.writeString(launchToken);
         dest.writeInt(flags);
         dest.writeInt(screenOrientation);
         dest.writeInt(configChanges);
@@ -1282,6 +1283,7 @@
         permission = source.readString();
         taskAffinity = source.readString();
         targetActivity = source.readString();
+        launchToken = source.readString();
         flags = source.readInt();
         screenOrientation = source.readInt();
         configChanges = source.readInt();
diff --git a/core/java/android/content/pm/InstantAppInfo.java b/core/java/android/content/pm/InstantAppInfo.java
index 67afc92..cb04fc3 100644
--- a/core/java/android/content/pm/InstantAppInfo.java
+++ b/core/java/android/content/pm/InstantAppInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -31,6 +32,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class InstantAppInfo implements Parcelable {
     private final ApplicationInfo mApplicationInfo;
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 68bb562..ebeaad7 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1258,9 +1258,12 @@
                 }
             }
 
-            pkg.setCodePath(packageDir.getAbsolutePath());
+            pkg.setCodePath(packageDir.getCanonicalPath());
             pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
+        } catch (IOException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed to get path: " + lite.baseCodePath, e);
         } finally {
             IoUtils.closeQuietly(assetLoader);
         }
@@ -1289,9 +1292,12 @@
 
         try {
             final Package pkg = parseBaseApk(apkFile, assets, flags);
-            pkg.setCodePath(apkFile.getAbsolutePath());
+            pkg.setCodePath(apkFile.getCanonicalPath());
             pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
+        } catch (IOException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed to get path: " + apkFile, e);
         } finally {
             IoUtils.closeQuietly(assets);
         }
diff --git a/core/java/android/database/OWNERS b/core/java/android/database/OWNERS
new file mode 100644
index 0000000..84e81e4
--- /dev/null
+++ b/core/java/android/database/OWNERS
@@ -0,0 +1,2 @@
+fkupolov@google.com
+omakoto@google.com
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index b7e353a..52527ed 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -16,43 +16,48 @@
 package android.hardware.location;
 
 import android.annotation.RequiresPermission;
-import android.os.Handler;
+import android.os.RemoteException;
+
+import dalvik.system.CloseGuard;
 
 import java.io.Closeable;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * A class describing a client of the Context Hub Service.
  *
- * Clients can send messages to nanoapps at a Context Hub through this object.
+ * Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported
+ * by this object are thread-safe and can be used without external synchronization.
  *
  * @hide
  */
 public class ContextHubClient implements Closeable {
     /*
-     * The ContextHubClient interface associated with this client.
+     * The proxy to the client interface at the service.
      */
-    // TODO: Implement this interface and associate with ContextHubClient object
-    // private final IContextHubClient mClientInterface;
+    private final IContextHubClient mClientProxy;
 
     /*
-     * The listening callback associated with this client.
+     * The callback interface associated with this client.
      */
-    private ContextHubClientCallback mCallback;
+    private final IContextHubClientCallback mCallbackInterface;
 
     /*
      * The Context Hub that this client is attached to.
      */
-    private ContextHubInfo mAttachedHub;
+    private final ContextHubInfo mAttachedHub;
 
-    /*
-     * The handler to invoke mCallback.
-     */
-    private Handler mCallbackHandler;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
 
-    ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
-        mCallback = callback;
-        mCallbackHandler = handler;
+    private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+    /* package */ ContextHubClient(
+            IContextHubClient clientProxy, IContextHubClientCallback callback,
+            ContextHubInfo hubInfo) {
+        mClientProxy = clientProxy;
+        mCallbackInterface = callback;
         mAttachedHub = hubInfo;
+        mCloseGuard.open("close");
     }
 
     /**
@@ -71,7 +76,14 @@
      * All futures messages targeted for this client are dropped at the service.
      */
     public void close() {
-        throw new UnsupportedOperationException("TODO: Implement this");
+        if (!mIsClosed.getAndSet(true)) {
+            mCloseGuard.close();
+            try {
+                mClientProxy.close();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -90,6 +102,22 @@
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     @ContextHubTransaction.Result
     public int sendMessageToNanoApp(NanoAppMessage message) {
-        throw new UnsupportedOperationException("TODO: Implement this");
+        try {
+            return mClientProxy.sendMessageToNanoApp(message);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
     }
 }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 2411727..b31c7bc 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -456,6 +456,54 @@
     }
 
     /**
+     * Creates an interface to the ContextHubClient to send down to the service.
+     *
+     * @param callback the callback to invoke at the client process
+     * @param handler the handler to post callbacks for this client
+     *
+     * @return the callback interface
+     */
+    private IContextHubClientCallback createClientCallback(
+            ContextHubClientCallback callback, Handler handler) {
+        return new IContextHubClientCallback.Stub() {
+            @Override
+            public void onMessageFromNanoApp(NanoAppMessage message) {
+                handler.post(() -> callback.onMessageFromNanoApp(message));
+            }
+
+            @Override
+            public void onHubReset() {
+                handler.post(() -> callback.onHubReset());
+            }
+
+            @Override
+            public void onNanoAppAborted(long nanoAppId, int abortCode) {
+                handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
+            }
+
+            @Override
+            public void onNanoAppLoaded(long nanoAppId) {
+                handler.post(() -> callback.onNanoAppLoaded(nanoAppId));
+            }
+
+            @Override
+            public void onNanoAppUnloaded(long nanoAppId) {
+                handler.post(() -> callback.onNanoAppUnloaded(nanoAppId));
+            }
+
+            @Override
+            public void onNanoAppEnabled(long nanoAppId) {
+                handler.post(() -> callback.onNanoAppEnabled(nanoAppId));
+            }
+
+            @Override
+            public void onNanoAppDisabled(long nanoAppId) {
+                handler.post(() -> callback.onNanoAppDisabled(nanoAppId));
+            }
+        };
+    }
+
+    /**
      * Creates and registers a client and its callback with the Context Hub Service.
      *
      * A client is registered with the Context Hub Service for a specified Context Hub. When the
@@ -463,19 +511,37 @@
      * {@link ContextHubClient} object, and receive notifications through the provided callback.
      *
      * @param callback the notification callback to register
-     * @param hubInfo the hub to attach this client to
-     * @param handler the handler to invoke the callback, if null uses the main thread's Looper
-     *
+     * @param hubInfo  the hub to attach this client to
+     * @param handler  the handler to invoke the callback, if null uses the main thread's Looper
      * @return the registered client object
      *
-     * @see ContextHubClientCallback
+     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+     * @throws IllegalStateException    if there were too many registered clients at the service
+     * @throws NullPointerException     if callback or hubInfo is null
      *
      * @hide
+     * @see ContextHubClientCallback
      */
     public ContextHubClient createClient(
             ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
-        throw new UnsupportedOperationException(
-                "TODO: Implement this, and throw an exception on error");
+        if (callback == null) {
+            throw new NullPointerException("Callback cannot be null");
+        }
+        if (hubInfo == null) {
+            throw new NullPointerException("Hub info cannot be null");
+        }
+
+        Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler;
+        IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler);
+
+        IContextHubClient client;
+        try {
+            client = mService.createClient(clientInterface, hubInfo.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        return new ContextHubClient(client, clientInterface, hubInfo);
     }
 
     /**
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
new file mode 100644
index 0000000..d81126a
--- /dev/null
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.NanoAppMessage;
+
+/**
+ * @hide
+ */
+interface IContextHubClient {
+
+    // Sends a message to a nanoapp
+    int sendMessageToNanoApp(in NanoAppMessage message);
+
+    // Closes the connection with the Context Hub
+    void close();
+}
diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl
new file mode 100644
index 0000000..1c76bcb
--- /dev/null
+++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.NanoAppMessage;
+
+/**
+ * An interface used by the Context Hub Service to invoke callbacks for lifecycle notifications of a
+ * Context Hub and nanoapps, as well as for nanoapp messaging.
+ *
+ * @hide
+ */
+oneway interface IContextHubClientCallback {
+
+    // Callback invoked when receiving a message from a nanoapp.
+    void onMessageFromNanoApp(in NanoAppMessage message);
+
+    // Callback invoked when the attached Context Hub has reset.
+    void onHubReset();
+
+    // Callback invoked when a nanoapp aborts at the attached Context Hub.
+    void onNanoAppAborted(long nanoAppId, int abortCode);
+
+    // Callback invoked when a nanoapp is loaded at the attached Context Hub.
+    void onNanoAppLoaded(long nanoAppId);
+
+    // Callback invoked when a nanoapp is unloaded from the attached Context Hub.
+    void onNanoAppUnloaded(long nanoAppId);
+
+    // Callback invoked when a nanoapp is enabled at the attached Context Hub.
+    void onNanoAppEnabled(long nanoAppId);
+
+    // Callback invoked when a nanoapp is disabled at the attached Context Hub.
+    void onNanoAppDisabled(long nanoAppId);
+}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index ff8c1d0..1bb7c8f 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -17,12 +17,14 @@
 package android.hardware.location;
 
 // Declare any non-default types here with import statements
-import android.hardware.location.ContextHubMessage;
 import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubMessage;
 import android.hardware.location.NanoApp;
-import android.hardware.location.NanoAppInstanceInfo;
 import android.hardware.location.NanoAppFilter;
+import android.hardware.location.NanoAppInstanceInfo;
 import android.hardware.location.IContextHubCallback;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
 
 /**
  * @hide
@@ -52,4 +54,7 @@
 
     // send a message to a nanoApp
     int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg);
+
+    // Creates a client to send and receive messages
+    IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
 }
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 13b9206..7836cd0 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -21,18 +21,16 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Paint.Align;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.Paint.Align;
 import android.graphics.Region.Op;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.Keyboard.Key;
 import android.media.AudioManager;
 import android.os.Handler;
 import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.GestureDetector;
@@ -986,6 +984,9 @@
 
     private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
         if (mAccessibilityManager.isEnabled()) {
+            if (!mAccessibilityManager.isObservedEventType(eventType)) {
+                return;
+            }
             AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
             onInitializeAccessibilityEvent(event);
             final String text;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index ee75fd4..f468e5d 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -31,16 +31,10 @@
 import java.util.StringJoiner;
 
 /**
- * Representation of the capabilities of a network. This object serves two
- * purposes:
- * <ul>
- * <li>An expression of the current capabilities of an active network, typically
- * expressed through
+ * Representation of the capabilities of an active network. Instances are
+ * typically obtained through
  * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
  * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
- * <li>An expression of the future capabilities of a desired network, typically
- * expressed through {@link NetworkRequest}.
- * </ul>
  * <p>
  * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
  * network selection. Rather than indicate a need for Wi-Fi because an
@@ -79,7 +73,7 @@
      */
     public void clearAll() {
         mNetworkCapabilities = mTransportTypes = 0;
-        mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0;
+        mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
         mNetworkSpecifier = null;
         mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
     }
@@ -359,6 +353,7 @@
 
     /**
      * Sets all the capabilities set on this {@code NetworkCapability} instance.
+     * This overwrites any existing capabilities.
      *
      * @hide
      */
@@ -582,6 +577,7 @@
 
     /**
      * Sets all the transports set on this {@code NetworkCapability} instance.
+     * This overwrites any existing transports.
      *
      * @hide
      */
@@ -780,7 +776,7 @@
      * Signal strength. This is a signed integer, and higher values indicate better signal.
      * The exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
      */
-    private int mSignalStrength;
+    private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
 
     /**
      * Sets the signal strength. This is a signed integer, with higher values indicating a stronger
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 25b1705..97ded2d 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -32,7 +33,7 @@
      * The {@link NetworkCapabilities} that define this request.
      * @hide
      */
-    public final NetworkCapabilities networkCapabilities;
+    public final @NonNull NetworkCapabilities networkCapabilities;
 
     /**
      * Identifies the request.  NetworkRequests should only be constructed by
@@ -307,7 +308,7 @@
         return 0;
     }
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelable(networkCapabilities, flags);
+        networkCapabilities.writeToParcel(dest, flags);
         dest.writeInt(legacyType);
         dest.writeInt(requestId);
         dest.writeString(type.name());
@@ -315,7 +316,7 @@
     public static final Creator<NetworkRequest> CREATOR =
         new Creator<NetworkRequest>() {
             public NetworkRequest createFromParcel(Parcel in) {
-                NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
+                NetworkCapabilities nc = NetworkCapabilities.CREATOR.createFromParcel(in);
                 int legacyType = in.readInt();
                 int requestId = in.readInt();
                 Type type = Type.valueOf(in.readString());  // IllegalArgumentException if invalid.
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 95e3802..b00cb48 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Slog;
 
 /**
  * Snapshot of network state.
@@ -43,6 +44,16 @@
         this.network = network;
         this.subscriberId = subscriberId;
         this.networkId = networkId;
+
+        // This object is an atomic view of a network, so the various components
+        // should always agree on roaming state.
+        if (networkInfo != null && networkCapabilities != null) {
+            if (networkInfo.isRoaming() == networkCapabilities
+                    .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
+                Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo
+                        + " and " + networkCapabilities);
+            }
+        }
     }
 
     public NetworkState(Parcel in) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index af91e81..811091e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -224,6 +224,7 @@
      *   - Always On Display (screen doze mode) time and power
      * New in version 28:
      *   - Light/Deep Doze power
+     *   - WiFi Multicast Wakelock statistics (count & duration)
      */
     static final int CHECKIN_VERSION = 28;
 
@@ -313,6 +314,8 @@
     private static final String CAMERA_DATA = "cam";
     private static final String VIDEO_DATA = "vid";
     private static final String AUDIO_DATA = "aud";
+    private static final String WIFI_MULTICAST_TOTAL_DATA = "wmct";
+    private static final String WIFI_MULTICAST_DATA = "wmc";
 
     public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
 
@@ -516,6 +519,13 @@
         public abstract ArrayMap<String, ? extends Wakelock> getWakelockStats();
 
         /**
+         * Returns the WiFi Multicast Wakelock statistics.
+         *
+         * @return a Timer Object for the per uid Multicast statistics.
+         */
+        public abstract Timer getMulticastWakelockStats();
+
+        /**
          * Returns a mapping containing sync statistics.
          *
          * @return a Map from Strings to Timer objects.
@@ -3363,13 +3373,16 @@
                 screenDozeTime / 1000);
 
 
-        // Calculate wakelock times across all uids.
+        // Calculate both wakelock and wifi multicast wakelock times across all uids.
         long fullWakeLockTimeTotal = 0;
         long partialWakeLockTimeTotal = 0;
+        long multicastWakeLockTimeTotalMicros = 0;
+        int multicastWakeLockCountTotal = 0;
 
         for (int iu = 0; iu < NU; iu++) {
             final Uid u = uidStats.valueAt(iu);
 
+            // First calculating the wakelock stats
             final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
                     = u.getWakelockStats();
             for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -3387,6 +3400,13 @@
                         rawRealtime, which);
                 }
             }
+
+            // Now calculating the wifi multicast wakelock stats
+            final Timer mcTimer = u.getMulticastWakelockStats();
+            if (mcTimer != null) {
+                multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
+                multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
+            }
         }
 
         // Dump network stats
@@ -3502,6 +3522,11 @@
         }
         dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
 
+        // Dump Multicast total stats
+        dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA,
+                multicastWakeLockTimeTotalMicros / 1000,
+                multicastWakeLockCountTotal);
+
         if (which == STATS_SINCE_UNPLUGGED) {
             dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
                     getDischargeCurrentLevel());
@@ -3828,6 +3853,18 @@
                 }
             }
 
+            // WiFi Multicast Wakelock Statistics
+            final Timer mcTimer = u.getMulticastWakelockStats();
+            if (mcTimer != null) {
+                final long totalMcWakelockTimeMs =
+                        mcTimer.getTotalTimeLocked(rawRealtime, which) / 1000 ;
+                final int countMcWakelock = mcTimer.getCountLocked(which);
+                if(totalMcWakelockTimeMs > 0) {
+                    dumpLine(pw, uid, category, WIFI_MULTICAST_DATA,
+                            totalMcWakelockTimeMs, countMcWakelock);
+                }
+            }
+
             final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
             for (int isy=syncs.size()-1; isy>=0; isy--) {
                 final Timer timer = syncs.valueAt(isy);
@@ -4327,15 +4364,18 @@
             pw.print("  Connectivity changes: "); pw.println(connChanges);
         }
 
-        // Calculate wakelock times across all uids.
+        // Calculate both wakelock and wifi multicast wakelock times across all uids.
         long fullWakeLockTimeTotalMicros = 0;
         long partialWakeLockTimeTotalMicros = 0;
+        long multicastWakeLockTimeTotalMicros = 0;
+        int multicastWakeLockCountTotal = 0;
 
         final ArrayList<TimerEntry> timers = new ArrayList<>();
 
         for (int iu = 0; iu < NU; iu++) {
             final Uid u = uidStats.valueAt(iu);
 
+            // First calculate wakelock statistics
             final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
                     = u.getWakelockStats();
             for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -4363,6 +4403,13 @@
                     }
                 }
             }
+
+            // Next calculate wifi multicast wakelock statistics
+            final Timer mcTimer = u.getMulticastWakelockStats();
+            if (mcTimer != null) {
+                multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
+                multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
+            }
         }
 
         final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
@@ -4392,6 +4439,20 @@
             pw.println(sb.toString());
         }
 
+        if (multicastWakeLockTimeTotalMicros != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Total WiFi Multicast wakelock Count: ");
+            sb.append(multicastWakeLockCountTotal);
+            pw.println(sb.toString());
+
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Total WiFi Multicast wakelock time: ");
+            formatTimeMsNoSpace(sb, (multicastWakeLockTimeTotalMicros + 500) / 1000);
+            pw.println(sb.toString());
+        }
+
         pw.println("");
         pw.print(prefix);
         sb.setLength(0);
@@ -5309,6 +5370,24 @@
                 }
             }
 
+            // Calculate multicast wakelock stats
+            final Timer mcTimer = u.getMulticastWakelockStats();
+            if (mcTimer != null) {
+                final long multicastWakeLockTimeMicros = mcTimer.getTotalTimeLocked(rawRealtime, which);
+                final int multicastWakeLockCount = mcTimer.getCountLocked(which);
+
+                if (multicastWakeLockTimeMicros > 0) {
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    WiFi Multicast Wakelock");
+                    sb.append(" count = ");
+                    sb.append(multicastWakeLockCount);
+                    sb.append(" time = ");
+                    formatTimeMsNoSpace(sb, (multicastWakeLockTimeMicros + 500) / 1000);
+                    pw.println(sb.toString());
+                }
+            }
+
             final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
             for (int isy=syncs.size()-1; isy>=0; isy--) {
                 final Timer timer = syncs.valueAt(isy);
@@ -7085,6 +7164,10 @@
                 proto.end(wToken);
             }
 
+            // Wifi Multicast Wakelock (WIFI_MULTICAST_WAKELOCK_DATA)
+            dumpTimer(proto, UidProto.WIFI_MULTICAST_WAKELOCK, u.getMulticastWakelockStats(),
+                    rawRealtimeUs, which);
+
             // Wakeup alarms (WAKEUP_ALARM_DATA)
             for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
                 final Uid.Pkg ps = packageStats.valueAt(ipkg);
@@ -7341,6 +7424,30 @@
                 getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
         proto.end(mToken);
 
+        // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA)
+        // Calculate multicast wakelock stats across all uids.
+        long multicastWakeLockTimeTotalUs = 0;
+        int multicastWakeLockCountTotal = 0;
+
+        for (int iu = 0; iu < uidStats.size(); iu++) {
+            final Uid u = uidStats.valueAt(iu);
+
+            final Timer mcTimer = u.getMulticastWakelockStats();
+
+            if (mcTimer != null) {
+                multicastWakeLockTimeTotalUs +=
+                        mcTimer.getTotalTimeLocked(rawRealtimeUs, which);
+                multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
+            }
+        }
+
+        final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL);
+        proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS,
+                multicastWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.WifiMulticastWakelockTotal.COUNT,
+                multicastWakeLockCountTotal);
+        proto.end(wmctToken);
+
         // Power use item (POWER_USE_ITEM_DATA)
         final List<BatterySipper> sippers = helper.getUsageList();
         if (sippers != null) {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 6ebf3b9..02c7bd6 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -19,9 +19,11 @@
 import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.app.Application;
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.view.View;
 
 import com.android.internal.telephony.TelephonyProperties;
 
@@ -761,6 +763,80 @@
          * <p>Applications targeting this or a later release will get these
          * new changes in behavior:</p>
          * <ul>
+         * <li><a href="{@docRoot}about/versions/oreo/background.html">Background execution limits</a>
+         * are applied to the application.</li>
+         * <li>The behavior of AccountManager's
+         * {@link android.accounts.AccountManager#getAccountsByType},
+         * {@link android.accounts.AccountManager#getAccountsByTypeAndFeatures}, and
+         * {@link android.accounts.AccountManager#hasFeatures} has changed as documented there.</li>
+         * <li>{@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE_PRE_26}
+         * is now returned as
+         * {@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.</li>
+         * <li>The {@link android.app.NotificationManager} now requires the use of notification
+         * channels.</li>
+         * <li>Changes to the strict mode that are set in
+         * {@link Application#onCreate Application.onCreate} will no longer be clobbered after
+         * that function returns.</li>
+         * <li>A shared library apk with native code will have that native code included in
+         * the library path of its clients.</li>
+         * <li>{@link android.content.Context#getSharedPreferences Context.getSharedPreferences}
+         * in credential encrypted storage will throw an exception before the user is unlocked.</li>
+         * <li>Attempting to retrieve a {@link Context#FINGERPRINT_SERVICE} on a device that
+         * does not support that feature will now throw a runtime exception.</li>
+         * <li>{@link android.app.Fragment} will stop any active view animations when
+         * the fragment is stopped.</li>
+         * <li>Some compatibility code in Resources that attempts to use the default Theme
+         * the app may be using will be turned off, requiring the app to explicitly request
+         * resources with the right theme.</li>
+         * <li>{@link android.content.ContentResolver#notifyChange ContentResolver.notifyChange} and
+         * {@link android.content.ContentResolver#registerContentObserver
+         * ContentResolver.registerContentObserver}
+         * will throw a SecurityException if the caller does not have permission to access
+         * the provider (or the provider doesn't exit); otherwise the call will be silently
+         * ignored.</li>
+         * <li>{@link android.hardware.camera2.CameraDevice#createCaptureRequest
+         * CameraDevice.createCaptureRequest} will enable
+         * {@link android.hardware.camera2.CaptureRequest#CONTROL_ENABLE_ZSL} by default for
+         * still image capture.</li>
+         * <li>WallpaperManager's {@link android.app.WallpaperManager#getWallpaperFile},
+         * {@link android.app.WallpaperManager#getDrawable},
+         * {@link android.app.WallpaperManager#getFastDrawable},
+         * {@link android.app.WallpaperManager#peekDrawable}, and
+         * {@link android.app.WallpaperManager#peekFastDrawable} will throw an exception
+         * if you can not access the wallpaper.</li>
+         * <li>The behavior of
+         * {@link android.hardware.usb.UsbDeviceConnection#requestWait UsbDeviceConnection.requestWait}
+         * is modified as per the documentation there.</li>
+         * <li>{@link StrictMode.VmPolicy.Builder#detectAll StrictMode.VmPolicy.Builder.detectAll}
+         * will also enable {@link StrictMode.VmPolicy.Builder#detectContentUriWithoutPermission}
+         * and {@link StrictMode.VmPolicy.Builder#detectUntaggedSockets}.</li>
+         * <li>{@link StrictMode.ThreadPolicy.Builder#detectAll StrictMode.ThreadPolicy.Builder.detectAll}
+         * will also enable {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo}.</li>
+         * <li>{@link android.provider.DocumentsContract}'s various methods will throw failure
+         * exceptions back to the caller instead of returning null.
+         * <li>{@link View#hasFocusable View.hasFocusable} now includes auto-focusable views.</li>
+         * <li>{@link android.view.SurfaceView} will no longer always change the underlying
+         * Surface object when something about it changes; apps need to look at the current
+         * state of the object to determine which things they are interested in have changed.</li>
+         * <li>{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} must be
+         * used for overlay windows, other system overlay window types are not allowed.</li>
+         * <li>{@link android.view.ViewTreeObserver#addOnDrawListener
+         * ViewTreeObserver.addOnDrawListener} will throw an exception if called from within
+         * onDraw.</li>
+         * <li>{@link android.graphics.Canvas#setBitmap Canvas.setBitmap} will no longer preserve
+         * the current matrix and clip stack of the canvas.</li>
+         * <li>{@link android.widget.ListPopupWindow#setHeight ListPopupWindow.setHeight}
+         * will throw an exception if a negative height is supplied.</li>
+         * <li>{@link android.widget.TextView} will use internationalized input for numbers,
+         * dates, and times.</li>
+         * <li>{@link android.widget.Toast} must be used for showing toast windows; the toast
+         * window type can not be directly used.</li>
+         * <li>{@link android.net.wifi.WifiManager#getConnectionInfo WifiManager.getConnectionInfo}
+         * requires that the caller hold the location permission to return BSSID/SSID</li>
+         * <li>{@link android.net.wifi.p2p.WifiP2pManager#requestPeers WifiP2pManager.requestPeers}
+         * requires the caller hold the location permission.</li>
+         * <li>{@link android.R.attr#maxAspectRatio} defaults to 0, meaning there is no restriction
+         * on the app's maximum aspect ratio (so it can be stretched to fill larger screens).</li>
          * <li>{@link android.R.attr#focusable} defaults to a new state ({@code auto}) where it will
          * inherit the value of {@link android.R.attr#clickable} unless explicitly overridden.</li>
          * <li>A default theme-appropriate focus-state highlight will be supplied to all Views
@@ -772,6 +848,15 @@
 
         /**
          * O MR1.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li>Apps exporting and linking to apk shared libraries must explicitly
+         * enumerate all signing certificates in a consistent order.</li>
+         * <li>{@link android.R.attr#screenOrientation} can not be used to request a fixed
+         * orientation if the associated activity is not fullscreen and opaque.</li>
+         * </ul>
          */
         public static final int O_MR1 = 27;
 
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index 3314f60..1d2a408 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -56,4 +56,7 @@
 
     /** Send a broadcast to the specified pkg and class that it should getData now. */
     oneway void sendBroadcast(String pkg, String cls);
+
+    /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
+    oneway void triggerUidSnapshot();
 }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 36121d4..62bb385 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Size;
 import android.util.SizeF;
@@ -1866,7 +1867,14 @@
             if (remoteStackTrace != null) {
                 RemoteException cause = new RemoteException(
                         "Remote stack trace:\n" + remoteStackTrace, null, false, false);
-                e.initCause(cause);
+                try {
+                    Throwable rootCause = ExceptionUtils.getRootCause(e);
+                    if (rootCause != null) {
+                        rootCause.initCause(cause);
+                    }
+                } catch (RuntimeException ex) {
+                    Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
+                }
             }
             SneakyThrow.sneakyThrow(e);
         }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5dd8d05..068f5f7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -555,6 +555,11 @@
         int FORCE_ALL_APPS_STANDBY = 11;
 
         /**
+         * Whether to enable background check on all apps or not.
+         */
+        int FORCE_BACKGROUND_CHECK = 12;
+
+        /**
          * Whether to disable non-essential sensors. (e.g. edge sensors.)
          */
         int OPTIONAL_SENSORS = 13;
diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java
index 5c80ca6..69a3922 100644
--- a/core/java/android/os/UEventObserver.java
+++ b/core/java/android/os/UEventObserver.java
@@ -108,7 +108,7 @@
      * UEventObserver after this call. Repeated calls have no effect.
      */
     public final void stopObserving() {
-        final UEventThread t = getThread();
+        final UEventThread t = peekThread();
         if (t != null) {
             t.removeObserver(this);
         }
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 2ffc7b0..3b53260 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -112,7 +112,7 @@
      * path belongs to a volume managed by vold, and that path is either
      * external storage data or OBB directory belonging to calling app.
      */
-    int mkdirs(in String callingPkg, in String path) = 34;
+    void mkdirs(in String callingPkg, in String path) = 34;
     /**
      * Determines the type of the encryption password
      * @return PasswordType
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 0b007dd..4796712 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1123,6 +1123,15 @@
         return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
     }
 
+    /** {@hide} */
+    public void mkdirs(File file) {
+        try {
+            mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @removed */
     public @NonNull StorageVolume[] getVolumeList() {
         return getVolumeList(mContext.getUserId(), 0);
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index ff8b9dd..adf437c 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -62,11 +62,25 @@
     public static final String NON_INDEXABLES_KEYS = "non_indexables_key";
 
     /**
+     * Site map pairs data key
+     *
+     * @hide
+     */
+    public static final String SITE_MAP_PAIRS_KEYS = "site_map_pairs";
+
+    /**
      * ContentProvider path for non indexable data keys.
      */
     public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS;
 
     /**
+     * ContentProvider path for sitemap keys.
+     *
+     * @hide
+     */
+    public static final String SITE_MAP_PAIRS_PATH = SETTINGS + "/" + SITE_MAP_PAIRS_KEYS;
+
+    /**
      * Indexable xml resources columns.
      */
     public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] {
@@ -113,6 +127,18 @@
     };
 
     /**
+     * Columns for site map queries.
+     *
+     * @hide
+     */
+    public static final String[] SITE_MAP_COLUMNS = new String[] {
+            SiteMapColumns.PARENT_CLASS,
+            SiteMapColumns.PARENT_TITLE,
+            SiteMapColumns.CHILD_CLASS,
+            SiteMapColumns.CHILD_TITLE,
+    };
+
+    /**
      * Indexable raw data columns indices.
      */
     public static final int COLUMN_INDEX_RAW_RANK = 0;
@@ -169,6 +195,16 @@
     }
 
     /**
+     * @hide
+     */
+    public static final class SiteMapColumns {
+        public static final String PARENT_CLASS = "parent_class";
+        public static final String CHILD_CLASS = "child_class";
+        public static final String PARENT_TITLE = "parent_title";
+        public static final String CHILD_TITLE = "child_title";
+    }
+
+    /**
      * Constants related to a {@link SearchIndexableData}.
      *
      * This is the raw data that is stored into an Index. This is related to
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index 3120e54..138e77b 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -72,6 +72,7 @@
     private static final int MATCH_RES_CODE = 1;
     private static final int MATCH_RAW_CODE = 2;
     private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3;
+    private static final int MATCH_SITE_MAP_PAIRS_CODE = 4;
 
     /**
      * Implementation is provided by the parent class.
@@ -87,6 +88,8 @@
                 MATCH_RAW_CODE);
         mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH,
                 MATCH_NON_INDEXABLE_KEYS_CODE);
+        mMatcher.addURI(mAuthority, SearchIndexablesContract.SITE_MAP_PAIRS_PATH,
+                MATCH_SITE_MAP_PAIRS_CODE);
 
         // Sanity check our setup
         if (!info.exported) {
@@ -112,6 +115,8 @@
                 return queryRawData(null);
             case MATCH_NON_INDEXABLE_KEYS_CODE:
                 return queryNonIndexableKeys(null);
+            case MATCH_SITE_MAP_PAIRS_CODE:
+                return querySiteMapPairs();
             default:
                 throw new UnsupportedOperationException("Unknown Uri " + uri);
         }
@@ -150,6 +155,17 @@
      */
     public abstract Cursor queryNonIndexableKeys(String[] projection);
 
+    /**
+     * Returns a {@link Cursor}, where rows are [parent class, child class] entries to form a site
+     * map. The list of pairs should be as complete as possible.
+     *
+     * @hide
+     */
+    public Cursor querySiteMapPairs() {
+        // By default no-op.
+        return null;
+    }
+
     @Override
     public String getType(Uri uri) {
         switch (mMatcher.match(uri)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9945755..1e0948a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9379,6 +9379,9 @@
         /** {@hide} */
         public static final String
                 BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
+        /** {@hide} */
+        public static final String
+                BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
 
         /**
          * Activity manager specific settings.
@@ -9745,6 +9748,14 @@
         }
 
         /**
+         * Get the key that retrieves a bluetooth hearing aid priority.
+         * @hide
+         */
+        public static final String getBluetoothHearingAidPriorityKey(String address) {
+            return BLUETOOTH_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+        }
+
+        /**
          * Get the key that retrieves a bluetooth map priority.
          * @hide
          */
@@ -10445,6 +10456,15 @@
                 "storage_settings_clobber_threshold";
 
         /**
+         * If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link Secure#LOCATION_MODE_OFF}
+         * temporarily for all users.
+         *
+         * @hide
+         */
+        public static final String LOCATION_GLOBAL_KILL_SWITCH =
+                "location_global_kill_switch";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 953501c..cd362c7 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,8 +438,21 @@
  *  AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
  *
  *  save(username, password);
- *
  * </pre>
+ *
+ *
+ * <a name="Privacy"></a>
+ * <h3>Privacy</h3>
+ *
+ * <p>The {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} method is called
+ * without the user content. The Android system strips some properties of the
+ * {@link android.app.assist.AssistStructure.ViewNode view nodes} passed to these calls, but not all
+ * of them. For example, the data provided in the {@link android.view.ViewStructure.HtmlInfo}
+ * objects set by {@link android.webkit.WebView} is never stripped out.
+ *
+ * <p>Because this data could contain PII (Personally Identifiable Information, such as username or
+ * email address), the service should only use it locally (i.e., in the app's process) for
+ * heuristics purposes, but it should not be sent to external servers.
  */
 public abstract class AutofillService extends Service {
     private static final String TAG = "AutofillService";
diff --git a/core/java/android/service/autofill/ISaveCallback.aidl b/core/java/android/service/autofill/ISaveCallback.aidl
index e260c73..a9364fe 100644
--- a/core/java/android/service/autofill/ISaveCallback.aidl
+++ b/core/java/android/service/autofill/ISaveCallback.aidl
@@ -16,12 +16,14 @@
 
 package android.service.autofill;
 
+import android.content.IntentSender;
+
 /**
  * Interface to receive the result of a save request.
  *
  * @hide
  */
 interface ISaveCallback {
-    void onSuccess();
+    void onSuccess(in IntentSender intentSender);
     void onFailure(CharSequence message);
 }
diff --git a/core/java/android/service/autofill/NegationValidator.java b/core/java/android/service/autofill/NegationValidator.java
new file mode 100644
index 0000000..a963f9f
--- /dev/null
+++ b/core/java/android/service/autofill/NegationValidator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Validator used to implement a {@code NOT} logical operation.
+ *
+ * @hide
+ */
+final class NegationValidator extends InternalValidator {
+    @NonNull private final InternalValidator mValidator;
+
+    NegationValidator(@NonNull InternalValidator validator) {
+        mValidator = Preconditions.checkNotNull(validator);
+    }
+
+    @Override
+    public boolean isValid(@NonNull ValueFinder finder) {
+        return !mValidator.isValid(finder);
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "NegationValidator: [validator=" + mValidator + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mValidator, flags);
+    }
+
+    public static final Parcelable.Creator<NegationValidator> CREATOR =
+            new Parcelable.Creator<NegationValidator>() {
+        @Override
+        public NegationValidator createFromParcel(Parcel parcel) {
+            return new NegationValidator(parcel.readParcelable(null));
+        }
+
+        @Override
+        public NegationValidator[] newArray(int size) {
+            return new NegationValidator[size];
+        }
+    };
+}
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 7207f1d..855981a 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -16,9 +16,14 @@
 
 package android.service.autofill;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
+import android.content.IntentSender;
 import android.os.RemoteException;
 
+import com.android.internal.util.Preconditions;
+
 /**
  * Handles save requests from the {@link AutofillService} into the {@link Activity} being
  * autofilled.
@@ -36,18 +41,33 @@
      * Notifies the Android System that an
      * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
      * by the service.
-     *
-     * <p>If the service could not handle the request right away&mdash;for example, because it must
-     * launch an activity asking the user to authenticate first or because the network is
-     * down&mdash;it should still call {@link #onSuccess()}.
-     *
-     * @throws RuntimeException if an error occurred while calling the Android System.
      */
     public void onSuccess() {
+        onSuccessInternal(null);
+    }
+
+    /**
+     * Notifies the Android System that an
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
+     * by the service.
+     *
+     * <p>This method is useful when the service requires extra work&mdash;for example, launching an
+     * activity asking the user to authenticate first &mdash;before it can process the request,
+     * as the intent will be launched from the context of the activity being autofilled and hence
+     * will be part of that activity's stack.
+     *
+     * @param intentSender intent that will be launched from the context of activity being
+     * autofilled.
+     */
+    public void onSuccess(@NonNull IntentSender intentSender) {
+        onSuccessInternal(Preconditions.checkNotNull(intentSender));
+    }
+
+    private void onSuccessInternal(@Nullable IntentSender intentSender) {
         assertNotCalled();
         mCalled = true;
         try {
-            mCallback.onSuccess();
+            mCallback.onSuccess(intentSender);
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
@@ -63,11 +83,10 @@
      * the {@link SaveRequest} and call {@link #onSuccess()} instead.
      *
      * <p><b>Note:</b> The Android System displays an UI with the supplied error message; if
-     * you prefer to show your own message, call {@link #onSuccess()} instead.
+     * you prefer to show your own message, call {@link #onSuccess()} or
+     * {@link #onSuccess(IntentSender)} instead.
      *
      * @param message error message to be displayed to the user.
-     *
-     * @throws RuntimeException if an error occurred while calling the Android System.
      */
     public void onFailure(CharSequence message) {
         assertNotCalled();
diff --git a/core/java/android/service/autofill/Validators.java b/core/java/android/service/autofill/Validators.java
index 51b503c..1c83868 100644
--- a/core/java/android/service/autofill/Validators.java
+++ b/core/java/android/service/autofill/Validators.java
@@ -52,6 +52,19 @@
         return new OptionalValidators(getInternalValidators(validators));
     }
 
+    /**
+     * Creates a validator that is valid only if {@code validator} is not.
+     *
+     * @throws IllegalArgumentException if {@code validator} is an instance of a class that is not
+     * provided by the Android System.
+     */
+    @NonNull
+    public static Validator not(@NonNull Validator validator) {
+        Preconditions.checkArgument(validator instanceof InternalValidator,
+                "validator not provided by Android System: " + validator);
+        return new NegationValidator((InternalValidator) validator);
+    }
+
     private static InternalValidator[] getInternalValidators(Validator[] validators) {
         Preconditions.checkArrayElementsNotNull(validators, "validators");
 
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index cd233b8..df0842f 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -105,6 +105,13 @@
     public static final String EXTRA_RESOLUTION_CALLING_PACKAGE =
             "android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE";
 
+    /**
+     * Intent extra set for resolution requests containing a boolean indicating whether to ask the
+     * user to retry another confirmation code.
+     */
+    public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED =
+            "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED";
+
     /** Result code for a successful operation. */
     public static final int RESULT_OK = 0;
     /** Result code indicating that an active SIM must be deactivated to perform the operation. */
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1c6275f..dd0ae33 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -889,7 +889,8 @@
                             mFinalStableInsets.set(mDispatchedStableInsets);
                             WindowInsets insets = new WindowInsets(mFinalSystemInsets,
                                     null, mFinalStableInsets,
-                                    getResources().getConfiguration().isScreenRound(), false);
+                                    getResources().getConfiguration().isScreenRound(), false,
+                                    null /* displayCutout */);
                             if (DEBUG) {
                                 Log.v(TAG, "dispatching insets=" + insets);
                             }
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
new file mode 100644
index 0000000..19cd42e
--- /dev/null
+++ b/core/java/android/view/DisplayCutout.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a part of the display that is not functional for displaying content.
+ *
+ * <p>{@code DisplayCutout} is immutable.
+ *
+ * @hide will become API
+ */
+public final class DisplayCutout {
+
+    private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
+    private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();
+
+    /**
+     * An instance where {@link #hasCutout()} returns {@code false}.
+     *
+     * @hide
+     */
+    public static final DisplayCutout NO_CUTOUT =
+            new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);
+
+    private final Rect mSafeInsets;
+    private final Rect mBoundingRect;
+    private final List<Point> mBoundingPolygon;
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
+        mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
+        mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
+        mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
+    }
+
+    /**
+     * Returns whether there is a cutout.
+     *
+     * If false, the safe insets will all return zero, and the bounding box or polygon will be
+     * empty or outside the content view.
+     *
+     * @return {@code true} if there is a cutout, {@code false} otherwise
+     */
+    public boolean hasCutout() {
+        return !mSafeInsets.equals(ZERO_RECT);
+    }
+
+    /** Returns the inset from the top which avoids the display cutout. */
+    public int getSafeInsetTop() {
+        return mSafeInsets.top;
+    }
+
+    /** Returns the inset from the bottom which avoids the display cutout. */
+    public int getSafeInsetBottom() {
+        return mSafeInsets.bottom;
+    }
+
+    /** Returns the inset from the left which avoids the display cutout. */
+    public int getSafeInsetLeft() {
+        return mSafeInsets.left;
+    }
+
+    /** Returns the inset from the right which avoids the display cutout. */
+    public int getSafeInsetRight() {
+        return mSafeInsets.right;
+    }
+
+    /**
+     * Obtains the safe insets in a rect.
+     *
+     * @param out a rect which is set to the safe insets.
+     * @hide
+     */
+    public void getSafeInsets(@NonNull Rect out) {
+        out.set(mSafeInsets);
+    }
+
+    /**
+     * Obtains the bounding rect of the cutout.
+     *
+     * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
+     *         to the top-left corner of the content view.
+     */
+    public void getBoundingRect(@NonNull Rect outRect) {
+        outRect.set(mBoundingRect);
+    }
+
+    /**
+     * Obtains the bounding polygon of the cutout.
+     *
+     * @param outPolygon is filled with a list of points representing the corners of a convex
+     *         polygon which covers the cutout. Coordinates are relative to the
+     *         top-left corner of the content view.
+     */
+    public void getBoundingPolygon(List<Point> outPolygon) {
+        outPolygon.clear();
+        for (int i = 0; i < mBoundingPolygon.size(); i++) {
+            outPolygon.add(new Point(mBoundingPolygon.get(i)));
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mSafeInsets.hashCode();
+        result = result * 31 + mBoundingRect.hashCode();
+        result = result * 31 + mBoundingPolygon.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof DisplayCutout) {
+            DisplayCutout c = (DisplayCutout) o;
+            return mSafeInsets.equals(c.mSafeInsets)
+                    && mBoundingRect.equals(c.mBoundingRect)
+                    && mBoundingPolygon.equals(c.mBoundingPolygon);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayCutout{insets=" + mSafeInsets
+                + " bounding=" + mBoundingRect
+                + "}";
+    }
+
+    /**
+     * Insets the reference frame of the cutout in the given directions.
+     *
+     * @return a copy of this instance which has been inset
+     * @hide
+     */
+    public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+        if (mBoundingRect.isEmpty()
+                || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
+            return this;
+        }
+
+        Rect safeInsets = new Rect(mSafeInsets);
+        Rect boundingRect = new Rect(mBoundingRect);
+        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        getBoundingPolygon(boundingPolygon);
+
+        // Note: it's not really well defined what happens when the inset is negative, because we
+        // don't know if the safe inset needs to expand in general.
+        if (insetTop > 0 || safeInsets.top > 0) {
+            safeInsets.top = atLeastZero(safeInsets.top - insetTop);
+        }
+        if (insetBottom > 0 || safeInsets.bottom > 0) {
+            safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom);
+        }
+        if (insetLeft > 0 || safeInsets.left > 0) {
+            safeInsets.left = atLeastZero(safeInsets.left - insetLeft);
+        }
+        if (insetRight > 0 || safeInsets.right > 0) {
+            safeInsets.right = atLeastZero(safeInsets.right - insetRight);
+        }
+
+        boundingRect.offset(-insetLeft, -insetTop);
+        offset(boundingPolygon, -insetLeft, -insetTop);
+
+        return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+    }
+
+    /**
+     * Calculates the safe insets relative to the given reference frame.
+     *
+     * @return a copy of this instance with the safe insets calculated
+     * @hide
+     */
+    public DisplayCutout calculateRelativeTo(Rect frame) {
+        if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
+            return NO_CUTOUT;
+        }
+
+        Rect boundingRect = new Rect(mBoundingRect);
+        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        getBoundingPolygon(boundingPolygon);
+
+        return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
+    }
+
+    private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
+            ArrayList<Point> boundingPolygon) {
+        Rect safeRect = new Rect();
+        int bestArea = 0;
+        int bestVariant = 0;
+        for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
+            int area = calculateInsetVariantArea(frame, boundingRect, variant, safeRect);
+            if (bestArea < area) {
+                bestArea = area;
+                bestVariant = variant;
+            }
+        }
+        calculateInsetVariantArea(frame, boundingRect, bestVariant, safeRect);
+        if (safeRect.isEmpty()) {
+            // The entire frame overlaps with the cutout.
+            safeRect.set(0, frame.height(), 0, 0);
+        } else {
+            // Convert safeRect to insets relative to frame. We're reusing the rect here to avoid
+            // an allocation.
+            safeRect.set(
+                    Math.max(0, safeRect.left - frame.left),
+                    Math.max(0, safeRect.top - frame.top),
+                    Math.max(0, frame.right - safeRect.right),
+                    Math.max(0, frame.bottom - safeRect.bottom));
+        }
+
+        boundingRect.offset(-frame.left, -frame.top);
+        offset(boundingPolygon, -frame.left, -frame.top);
+
+        return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
+    }
+
+    private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
+            Rect outSafeRect) {
+        switch (variant) {
+            case ROTATION_0:
+                outSafeRect.set(frame.left, frame.top, frame.right, boundingRect.top);
+                break;
+            case ROTATION_90:
+                outSafeRect.set(frame.left, frame.top, boundingRect.left, frame.bottom);
+                break;
+            case ROTATION_180:
+                outSafeRect.set(frame.left, boundingRect.bottom, frame.right, frame.bottom);
+                break;
+            case ROTATION_270:
+                outSafeRect.set(boundingRect.right, frame.top, frame.right, frame.bottom);
+                break;
+        }
+
+        return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height();
+    }
+
+    private static int atLeastZero(int value) {
+        return value < 0 ? 0 : value;
+    }
+
+    private static void offset(ArrayList<Point> points, int dx, int dy) {
+        for (int i = 0; i < points.size(); i++) {
+            points.get(i).offset(dx, dy);
+        }
+    }
+
+    /**
+     * Creates an instance from a bounding polygon.
+     *
+     * @hide
+     */
+    public static DisplayCutout fromBoundingPolygon(List<Point> points) {
+        Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
+                Integer.MIN_VALUE, Integer.MIN_VALUE);
+        ArrayList<Point> boundingPolygon = new ArrayList<>();
+
+        for (int i = 0; i < points.size(); i++) {
+            Point point = points.get(i);
+            boundingRect.left = Math.min(boundingRect.left, point.x);
+            boundingRect.right = Math.max(boundingRect.right, point.x);
+            boundingRect.top = Math.min(boundingRect.top, point.y);
+            boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
+            boundingPolygon.add(new Point(point));
+        }
+
+        return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
+    }
+
+    /**
+     * Helper class for passing {@link DisplayCutout} through binder.
+     *
+     * Needed, because {@code readFromParcel} cannot be used with immutable classes.
+     *
+     * @hide
+     */
+    public static final class ParcelableWrapper implements Parcelable {
+
+        private DisplayCutout mInner;
+
+        public ParcelableWrapper() {
+            this(NO_CUTOUT);
+        }
+
+        public ParcelableWrapper(DisplayCutout cutout) {
+            mInner = cutout;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            if (mInner == NO_CUTOUT) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                out.writeTypedObject(mInner.mSafeInsets, flags);
+                out.writeTypedObject(mInner.mBoundingRect, flags);
+                out.writeTypedList(mInner.mBoundingPolygon, flags);
+            }
+        }
+
+        /**
+         * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing
+         * instance.
+         *
+         * Needed for AIDL out parameters.
+         */
+        public void readFromParcel(Parcel in) {
+            mInner = readCutout(in);
+        }
+
+        public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() {
+            @Override
+            public ParcelableWrapper createFromParcel(Parcel in) {
+                return new ParcelableWrapper(readCutout(in));
+            }
+
+            @Override
+            public ParcelableWrapper[] newArray(int size) {
+                return new ParcelableWrapper[size];
+            }
+        };
+
+        private static DisplayCutout readCutout(Parcel in) {
+            if (in.readInt() == 0) {
+                return NO_CUTOUT;
+            }
+
+            ArrayList<Point> boundingPolygon = new ArrayList<>();
+
+            Rect safeInsets = in.readTypedObject(Rect.CREATOR);
+            Rect boundingRect = in.readTypedObject(Rect.CREATOR);
+            in.readTypedList(boundingPolygon, Point.CREATOR);
+
+            return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+        }
+
+        public DisplayCutout get() {
+            return mInner;
+        }
+
+        public void set(ParcelableWrapper cutout) {
+            mInner = cutout.get();
+        }
+
+        public void set(DisplayCutout cutout) {
+            mInner = cutout;
+        }
+
+        @Override
+        public int hashCode() {
+            return mInner.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o instanceof ParcelableWrapper
+                    && mInner.equals(((ParcelableWrapper) o).mInner);
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mInner);
+        }
+    }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index aa2f1c1..3d01ec2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -56,7 +56,9 @@
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
             boolean allLayers, boolean useIdentityTransform);
     private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
-            int rotation);
+            Rect sourceCrop, float frameScale);
+    private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken,
+            Rect sourceCrop, float frameScale);
 
     private static native long nativeCreateTransaction();
     private static native long nativeGetNativeTransactionFinalizer();
@@ -1177,14 +1179,24 @@
      * Captures a layer and its children into the provided {@link Surface}.
      *
      * @param layerHandleToken The root layer to capture.
-     * @param consumer The {@link Surface} to capture the layer into.
-     * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
-     *                 Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
-     *                 native portrait orientation by default, so this is useful for returning
-     *                 captures that are independent of device orientation.
+     * @param consumer         The {@link Surface} to capture the layer into.
+     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
+     *                         Rect()' or null if no cropping is desired.
+     * @param frameScale       The desired scale of the returned buffer; the raw
+     *                         screen will be scaled up/down.
      */
-    public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
-        nativeCaptureLayers(layerHandleToken, consumer, rotation);
+    public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop,
+            float frameScale) {
+        nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale);
+    }
+
+    /**
+     * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this
+     * captures to a {@link GraphicBuffer} instead of a {@link Surface}.
+     */
+    public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop,
+            float frameScale) {
+        return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale);
     }
 
     public static class Transaction implements Closeable {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e36a298..0525ab1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2929,6 +2929,7 @@
      *        1                          PFLAG3_TEMPORARY_DETACH
      *       1                           PFLAG3_NO_REVEAL_ON_FOCUS
      *      1                            PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT
+     *     1                             PFLAG3_SCREEN_READER_FOCUSABLE
      * |-------|-------|-------|-------|
      */
 
@@ -3209,6 +3210,12 @@
      */
     static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x8000000;
 
+    /**
+     * Works like focusable for screen readers, but without the side effects on input focus.
+     * @see #setScreenReaderFocusable(boolean)
+     */
+    private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000;
+
     /* End of masks for mPrivateFlags3 */
 
     /**
@@ -5344,6 +5351,11 @@
                         setDefaultFocusHighlightEnabled(a.getBoolean(attr, true));
                     }
                     break;
+                case R.styleable.View_screenReaderFocusable:
+                    if (a.peekValue(attr) != null) {
+                        setScreenReaderFocusable(a.getBoolean(attr, false));
+                    }
+                    break;
             }
         }
 
@@ -7196,7 +7208,8 @@
      * @param text The announcement text.
      */
     public void announceForAccessibility(CharSequence text) {
-        if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
+        if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+                AccessibilityEvent.TYPE_ANNOUNCEMENT) && mParent != null) {
             AccessibilityEvent event = AccessibilityEvent.obtain(
                     AccessibilityEvent.TYPE_ANNOUNCEMENT);
             onInitializeAccessibilityEvent(event);
@@ -8394,6 +8407,7 @@
         info.setEnabled(isEnabled());
         info.setClickable(isClickable());
         info.setFocusable(isFocusable());
+        info.setScreenReaderFocusable(isScreenReaderFocusable());
         info.setFocused(isFocused());
         info.setAccessibilityFocused(isAccessibilityFocused());
         info.setSelected(isSelected());
@@ -10350,6 +10364,45 @@
     }
 
     /**
+     * Returns whether the view should be treated as a focusable unit by screen reader
+     * accessibility tools.
+     * @see #setScreenReaderFocusable(boolean)
+     *
+     * @return Whether the view should be treated as a focusable unit by screen reader.
+     */
+    public boolean isScreenReaderFocusable() {
+        return (mPrivateFlags3 & PFLAG3_SCREEN_READER_FOCUSABLE) != 0;
+    }
+
+    /**
+     * When screen readers (one type of accessibility tool) decide what should be read to the
+     * user, they typically look for input focusable ({@link #isFocusable()}) parents of
+     * non-focusable text items, and read those focusable parents and their non-focusable children
+     * as a unit. In some situations, this behavior is desirable for views that should not take
+     * input focus. Setting an item to be screen reader focusable requests that the view be
+     * treated as a unit by screen readers without any effect on input focusability. The default
+     * value of {@code false} lets screen readers use other signals, like focusable, to determine
+     * how to group items.
+     *
+     * @param screenReaderFocusable Whether the view should be treated as a unit by screen reader
+     *                              accessibility tools.
+     */
+    public void setScreenReaderFocusable(boolean screenReaderFocusable) {
+        int pflags3 = mPrivateFlags3;
+        if (screenReaderFocusable) {
+            pflags3 |= PFLAG3_SCREEN_READER_FOCUSABLE;
+        } else {
+            pflags3 &= ~PFLAG3_SCREEN_READER_FOCUSABLE;
+        }
+
+        if (pflags3 != mPrivateFlags3) {
+            mPrivateFlags3 = pflags3;
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        }
+    }
+
+    /**
      * Find the nearest view in the specified direction that can take focus.
      * This does not actually give focus to that view.
      *
@@ -10915,7 +10968,8 @@
         if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
             mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
             invalidate();
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) {
                 AccessibilityEvent event = AccessibilityEvent.obtain(
                         AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
                 event.setAction(action);
@@ -11740,7 +11794,8 @@
 
     private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
             int fromIndex, int toIndex) {
-        if (mParent == null) {
+        if (mParent == null || !AccessibilityManager.getInstance(mContext).isObservedEventType(
+                AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) {
             return;
         }
         AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -26130,7 +26185,8 @@
 
         @Override
         public void run() {
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+                    AccessibilityEvent.TYPE_VIEW_SCROLLED)) {
                 AccessibilityEvent event = AccessibilityEvent.obtain(
                         AccessibilityEvent.TYPE_VIEW_SCROLLED);
                 event.setScrollDeltaX(mDeltaX);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 929beae..122df93 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2591,7 +2591,7 @@
             boolean alreadyDispatchedToNewTouchTarget = false;
             if (!canceled && !intercepted) {
 
-                // If the event is targeting accessiiblity focus we give it to the
+                // If the event is targeting accessibility focus we give it to the
                 // view that has accessibility focus and if it does not handle it
                 // we clear the flag and dispatch the event to all children as usual.
                 // We are looking up the accessibility focused host to avoid keeping
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e9509b7..1c9d863 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1563,7 +1563,7 @@
             mLastWindowInsets = new WindowInsets(contentInsets,
                     null /* windowDecorInsets */, stableInsets,
                     mContext.getResources().getConfiguration().isScreenRound(),
-                    mAttachInfo.mAlwaysConsumeNavBar);
+                    mAttachInfo.mAlwaysConsumeNavBar, null /* displayCutout */);
         }
         return mLastWindowInsets;
     }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 750931a..df124ac 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -17,6 +17,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.graphics.Rect;
 
 /**
@@ -36,6 +37,7 @@
     private Rect mStableInsets;
     private Rect mTempRect;
     private boolean mIsRound;
+    private DisplayCutout mDisplayCutout;
 
     /**
      * In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -47,6 +49,7 @@
     private boolean mSystemWindowInsetsConsumed = false;
     private boolean mWindowDecorInsetsConsumed = false;
     private boolean mStableInsetsConsumed = false;
+    private boolean mCutoutConsumed = false;
 
     private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
 
@@ -59,12 +62,12 @@
     public static final WindowInsets CONSUMED;
 
     static {
-        CONSUMED = new WindowInsets(null, null, null, false, false);
+        CONSUMED = new WindowInsets(null, null, null, false, false, null);
     }
 
     /** @hide */
     public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
-            boolean isRound, boolean alwaysConsumeNavBar) {
+            boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
         mSystemWindowInsetsConsumed = systemWindowInsets == null;
         mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets;
 
@@ -76,6 +79,9 @@
 
         mIsRound = isRound;
         mAlwaysConsumeNavBar = alwaysConsumeNavBar;
+
+        mCutoutConsumed = displayCutout == null;
+        mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
     }
 
     /**
@@ -92,11 +98,13 @@
         mStableInsetsConsumed = src.mStableInsetsConsumed;
         mIsRound = src.mIsRound;
         mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
+        mDisplayCutout = src.mDisplayCutout;
+        mCutoutConsumed = src.mCutoutConsumed;
     }
 
     /** @hide */
     public WindowInsets(Rect systemWindowInsets) {
-        this(systemWindowInsets, null, null, false, false);
+        this(systemWindowInsets, null, null, false, false, null);
     }
 
     /**
@@ -260,10 +268,35 @@
      * @return true if any inset values are nonzero
      */
     public boolean hasInsets() {
-        return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets();
+        return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
+                || mDisplayCutout.hasCutout();
     }
 
     /**
+     * @return the display cutout
+     * @see DisplayCutout
+     * @hide pending API
+     */
+    @NonNull
+    public DisplayCutout getDisplayCutout() {
+        return mDisplayCutout;
+    }
+
+    /**
+     * Returns a copy of this WindowInsets with the cutout fully consumed.
+     *
+     * @return A modified copy of this WindowInsets
+     * @hide pending API
+     */
+    public WindowInsets consumeCutout() {
+        final WindowInsets result = new WindowInsets(this);
+        result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
+        result.mCutoutConsumed = true;
+        return result;
+    }
+
+
+    /**
      * Check if these insets have been fully consumed.
      *
      * <p>Insets are considered "consumed" if the applicable <code>consume*</code> methods
@@ -277,7 +310,8 @@
      * @return true if the insets have been fully consumed.
      */
     public boolean isConsumed() {
-        return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed;
+        return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
+                && mCutoutConsumed;
     }
 
     /**
@@ -495,7 +529,9 @@
     public String toString() {
         return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
                 + " windowDecorInsets=" + mWindowDecorInsets
-                + " stableInsets=" + mStableInsets +
-                (isRound() ? " round}" : "}");
+                + " stableInsets=" + mStableInsets
+                + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
+                + (isRound() ? " round" : "")
+                + "}";
     }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index eb5fc92..905c071 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -604,8 +604,10 @@
         public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
 
         /**
-         * Window type: panel that slides out from under the status bar
-         * In multiuser systems shows on all users' windows.
+         * Window type: panel that slides out from over the status bar
+         * In multiuser systems shows on all users' windows. These windows
+         * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL}
+         * windows.
          * @hide
          */
         public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index d785117..da5a1cd 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -23,8 +23,6 @@
 import android.util.LongSparseArray;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -33,8 +31,7 @@
  * It is updated when windows change or nodes change.
  * @hide
  */
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public final class AccessibilityCache {
+public class AccessibilityCache {
 
     private static final String LOG_TAG = "AccessibilityCache";
 
@@ -329,6 +326,8 @@
                 final long oldParentId = oldInfo.getParentNodeId();
                 if (info.getParentNodeId() != oldParentId) {
                     clearSubTreeLocked(windowId, oldParentId);
+                } else {
+                    oldInfo.recycle();
                 }
            }
 
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 5adea66..1d19a9f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -16,6 +16,7 @@
 
 package android.view.accessibility;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -23,6 +24,8 @@
 
 import com.android.internal.util.BitUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -709,6 +712,38 @@
      */
     public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
 
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_VIEW_CLICKED,
+            TYPE_VIEW_LONG_CLICKED,
+            TYPE_VIEW_SELECTED,
+            TYPE_VIEW_FOCUSED,
+            TYPE_VIEW_TEXT_CHANGED,
+            TYPE_WINDOW_STATE_CHANGED,
+            TYPE_NOTIFICATION_STATE_CHANGED,
+            TYPE_VIEW_HOVER_ENTER,
+            TYPE_VIEW_HOVER_EXIT,
+            TYPE_TOUCH_EXPLORATION_GESTURE_START,
+            TYPE_TOUCH_EXPLORATION_GESTURE_END,
+            TYPE_WINDOW_CONTENT_CHANGED,
+            TYPE_VIEW_SCROLLED,
+            TYPE_VIEW_TEXT_SELECTION_CHANGED,
+            TYPE_ANNOUNCEMENT,
+            TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+            TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+            TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+            TYPE_GESTURE_DETECTION_START,
+            TYPE_GESTURE_DETECTION_END,
+            TYPE_TOUCH_INTERACTION_START,
+            TYPE_TOUCH_INTERACTION_END,
+            TYPE_WINDOWS_CHANGED,
+            TYPE_VIEW_CONTEXT_CLICKED,
+            TYPE_ASSIST_READING_CONTEXT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventType {}
+
     /**
      * Mask for {@link AccessibilityEvent} all types.
      *
@@ -741,7 +776,7 @@
     private static final SynchronizedPool<AccessibilityEvent> sPool =
             new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
 
-    private int mEventType;
+    private @EventType int mEventType;
     private CharSequence mPackageName;
     private long mEventTime;
     int mMovementGranularity;
@@ -833,7 +868,7 @@
      *
      * @return The event type.
      */
-    public int getEventType() {
+    public @EventType int getEventType() {
         return mEventType;
     }
 
@@ -890,7 +925,7 @@
      *
      * @throws IllegalStateException If called from an AccessibilityService.
      */
-    public void setEventType(int eventType) {
+    public void setEventType(@EventType int eventType) {
         enforceNotSealed();
         mEventType = eventType;
     }
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index c3d6c69..d890f32 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,8 @@
 import android.util.LongSparseArray;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -86,6 +88,12 @@
     private static final LongSparseArray<AccessibilityInteractionClient> sClients =
         new LongSparseArray<>();
 
+    private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
+            new SparseArray<>();
+
+    private static AccessibilityCache sAccessibilityCache =
+            new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
+
     private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
 
     private final Object mInstanceLock = new Object();
@@ -100,12 +108,6 @@
 
     private Message mSameThreadMessage;
 
-    private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
-        new SparseArray<>();
-
-    private static final AccessibilityCache sAccessibilityCache =
-        new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
-
     /**
      * @return The client for the current thread.
      */
@@ -133,6 +135,50 @@
         }
     }
 
+    /**
+     * Gets a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     * @return The cached connection if such.
+     */
+    public static IAccessibilityServiceConnection getConnection(int connectionId) {
+        synchronized (sConnectionCache) {
+            return sConnectionCache.get(connectionId);
+        }
+    }
+
+    /**
+     * Adds a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     * @param connection The connection.
+     */
+    public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+        synchronized (sConnectionCache) {
+            sConnectionCache.put(connectionId, connection);
+        }
+    }
+
+    /**
+     * Removes a cached accessibility service connection.
+     *
+     * @param connectionId The connection id.
+     */
+    public static void removeConnection(int connectionId) {
+        synchronized (sConnectionCache) {
+            sConnectionCache.remove(connectionId);
+        }
+    }
+
+    /**
+     * This method is only for testing. Replacing the cache is a generally terrible idea, but
+     * tests need to be able to verify this class's interactions with the cache
+     */
+    @VisibleForTesting
+    public static void setCache(AccessibilityCache cache) {
+        sAccessibilityCache = cache;
+    }
+
     private AccessibilityInteractionClient() {
         /* reducing constructor visibility */
     }
@@ -300,7 +346,7 @@
                 if (success) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache);
                     if (infos != null && !infos.isEmpty()) {
                         for (int i = 1; i < infos.size(); i++) {
                             infos.get(i).recycle();
@@ -356,7 +402,7 @@
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
                     if (infos != null) {
-                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false);
                         return infos;
                     }
                 }
@@ -409,7 +455,7 @@
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
                     if (infos != null) {
-                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false);
                         return infos;
                     }
                 }
@@ -460,7 +506,7 @@
                 if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false);
                     return info;
                 }
             } else {
@@ -509,7 +555,7 @@
                 if (success) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
-                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false);
                     return info;
                 }
             } else {
@@ -731,13 +777,17 @@
      *
      * @param info The info.
      * @param connectionId The id of the connection to the system.
+     * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if
+     *                    this value is {@code false}
      */
     private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
-            int connectionId) {
+            int connectionId, boolean bypassCache) {
         if (info != null) {
             info.setConnectionId(connectionId);
             info.setSealed(true);
-            sAccessibilityCache.add(info);
+            if (!bypassCache) {
+                sAccessibilityCache.add(info);
+            }
         }
     }
 
@@ -746,14 +796,16 @@
      *
      * @param infos The {@link AccessibilityNodeInfo}s.
      * @param connectionId The id of the connection to the system.
+     * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if
+     *                    this value is {@code false}
      */
     private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
-            int connectionId) {
+            int connectionId, boolean bypassCache) {
         if (infos != null) {
             final int infosCount = infos.size();
             for (int i = 0; i < infosCount; i++) {
                 AccessibilityNodeInfo info = infos.get(i);
-                finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+                finalizeAndCacheAccessibilityNodeInfo(info, connectionId, bypassCache);
             }
         }
     }
@@ -773,41 +825,6 @@
     }
 
     /**
-     * Gets a cached accessibility service connection.
-     *
-     * @param connectionId The connection id.
-     * @return The cached connection if such.
-     */
-    public IAccessibilityServiceConnection getConnection(int connectionId) {
-        synchronized (sConnectionCache) {
-            return sConnectionCache.get(connectionId);
-        }
-    }
-
-    /**
-     * Adds a cached accessibility service connection.
-     *
-     * @param connectionId The connection id.
-     * @param connection The connection.
-     */
-    public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
-        synchronized (sConnectionCache) {
-            sConnectionCache.put(connectionId, connection);
-        }
-    }
-
-    /**
-     * Removes a cached accessibility service connection.
-     *
-     * @param connectionId The connection id.
-     */
-    public void removeConnection(int connectionId) {
-        synchronized (sConnectionCache) {
-            sConnectionCache.remove(connectionId);
-        }
-    }
-
-    /**
      * Checks whether the infos are a fully connected tree with no duplicates.
      *
      * @param infos The result list to check.
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 35f6acb..0375635 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -187,6 +188,7 @@
      *
      * @hide
      */
+    @TestApi
     public interface AccessibilityServicesStateChangeListener {
 
         /**
@@ -452,6 +454,18 @@
     }
 
     /**
+     * Returns whether there are observers registered for this event type. If
+     * this method returns false you shuold not generate events of this type
+     * to conserve resources.
+     *
+     * @param type The event type.
+     * @return Whether the event is being observed.
+     */
+    public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+        return mIsEnabled && (mRelevantEventTypes & type) != 0;
+    }
+
+    /**
      * Requests feedback interruption from all accessibility services.
      */
     public void interrupt() {
@@ -683,6 +697,7 @@
      *                for a callback on the process's main handler.
      * @hide
      */
+    @TestApi
     public void addAccessibilityServicesStateChangeListener(
             @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
         synchronized (mLock) {
@@ -698,6 +713,7 @@
      *
      * @hide
      */
+    @TestApi
     public void removeAccessibilityServicesStateChangeListener(
             @NonNull AccessibilityServicesStateChangeListener listener) {
         // Final CopyOnWriteArrayList - no lock needed.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9bdd3ff..faea920 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -635,6 +635,8 @@
 
     private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
 
+    private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
+
     private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
 
     /**
@@ -2321,6 +2323,37 @@
     }
 
     /**
+     * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
+     * that {@code false} indicates that it is not explicitly marked, not that the node is not
+     * a focusable unit. Screen readers should generally used other signals, such as
+     * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
+     * focus.
+     *
+     * @return {@code true} if the node is specifically marked as a focusable unit for screen
+     *         readers, {@code false} otherwise.
+     *
+     * @see View#isScreenReaderFocusable()
+     */
+    public boolean isScreenReaderFocusable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE);
+    }
+
+    /**
+     * Sets whether the node should be considered a focusable unit by a screen reader.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param screenReaderFocusable {@code true} if the node is a focusable unit for screen readers,
+     *                              {@code false} otherwise.
+     */
+    public void setScreenReaderFocusable(boolean screenReaderFocusable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE, screenReaderFocusable);
+    }
+
+    /**
      * Returns whether the node's text represents a hint for the user to enter text. It should only
      * be {@code true} if the node has editable text.
      *
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 2779aa2..f675c35 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -31,6 +31,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Information for generating a widget to handle classified text.
@@ -42,7 +43,7 @@
  *
  * <pre>{@code
  *   // Called preferably outside the UiThread.
- *   TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *   TextClassification classification = textClassifier.classifyText(allText, 10, 25);
  *
  *   // Called on the UiThread.
  *   Button button = new Button(context);
@@ -55,7 +56,7 @@
  *
  * <pre>{@code
  *   // Called preferably outside the UiThread.
- *   final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *   final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
  *
  *   // Called on the UiThread.
  *   view.startActionMode(new ActionMode.Callback() {
@@ -281,8 +282,8 @@
 
     @Override
     public String toString() {
-        return String.format("TextClassification {"
-                        + "text=%s, entities=%s, labels=%s, intents=%s}",
+        return String.format(Locale.US,
+                "TextClassification {text=%s, entities=%s, labels=%s, intents=%s}",
                 mText, mEntityConfidence, mLabels, mIntents);
     }
 
@@ -421,7 +422,7 @@
         }
 
         /**
-         * Ensures that we have at we have storage for the default action.
+         * Ensures that we have storage for the default action.
          */
         private void ensureDefaultActionAvailable() {
             if (mIntents.isEmpty()) mIntents.add(null);
@@ -441,7 +442,7 @@
     }
 
     /**
-     * TextClassification optional input parameters.
+     * Optional input parameters for generating TextClassification.
      */
     public static final class Options {
 
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index aeb8489..5aaa5ad 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -23,6 +23,8 @@
 import android.annotation.WorkerThread;
 import android.os.LocaleList;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -37,8 +39,7 @@
     /** @hide */
     String DEFAULT_LOG_TAG = "TextClassifierImpl";
 
-    /** @hide */
-    String TYPE_UNKNOWN = "";  // TODO: Make this public API.
+    String TYPE_UNKNOWN = "";
     String TYPE_OTHER = "other";
     String TYPE_EMAIL = "email";
     String TYPE_PHONE = "phone";
@@ -70,6 +71,8 @@
      *
      * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
      *      selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
+     *
+     * @see #suggestSelection(CharSequence, int, int)
      */
     @WorkerThread
     @NonNull
@@ -78,13 +81,46 @@
             @IntRange(from = 0) int selectionStartIndex,
             @IntRange(from = 0) int selectionEndIndex,
             @Nullable TextSelection.Options options) {
+        Utils.validateInput(text, selectionStartIndex, selectionEndIndex);
         return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
     }
 
     /**
+     * Returns suggested text selection start and end indices, recognized entity types, and their
+     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
+     * calls this method, a stack overflow error will happen.
+     *
+     * @param text text providing context for the selected text (which is specified
+     *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+     * @param selectionStartIndex start index of the selected part of text
+     * @param selectionEndIndex end index of the selected part of text
+     *
+     * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+     *      selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
+     *
      * @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
      */
-    // TODO: Consider deprecating (b/68846316)
+    @WorkerThread
+    @NonNull
+    default TextSelection suggestSelection(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int selectionStartIndex,
+            @IntRange(from = 0) int selectionEndIndex) {
+        return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+                (TextSelection.Options) null);
+    }
+
+    /**
+     * See {@link #suggestSelection(CharSequence, int, int)} or
+     * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
+     * calls this method, a stack overflow error will happen.
+     */
     @WorkerThread
     @NonNull
     default TextSelection suggestSelection(
@@ -92,7 +128,10 @@
             @IntRange(from = 0) int selectionStartIndex,
             @IntRange(from = 0) int selectionEndIndex,
             @Nullable LocaleList defaultLocales) {
-        return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+        final TextSelection.Options options = (defaultLocales != null)
+                ? new TextSelection.Options().setDefaultLocales(defaultLocales)
+                : null;
+        return suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
     }
 
     /**
@@ -107,6 +146,8 @@
      *
      * @throws IllegalArgumentException if text is null; startIndex is negative;
      *      endIndex is greater than text.length() or not greater than startIndex
+     *
+     * @see #classifyText(CharSequence, int, int)
      */
     @WorkerThread
     @NonNull
@@ -115,13 +156,45 @@
             @IntRange(from = 0) int startIndex,
             @IntRange(from = 0) int endIndex,
             @Nullable TextClassification.Options options) {
+        Utils.validateInput(text, startIndex, endIndex);
         return TextClassification.EMPTY;
     }
 
     /**
+     * Classifies the specified text and returns a {@link TextClassification} object that can be
+     * used to generate a widget for handling the classified text.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
+     * calls this method, a stack overflow error will happen.
+     *
+     * @param text text providing context for the text to classify (which is specified
+     *      by the sub sequence starting at startIndex and ending at endIndex)
+     * @param startIndex start index of the text to classify
+     * @param endIndex end index of the text to classify
+     *
+     * @throws IllegalArgumentException if text is null; startIndex is negative;
+     *      endIndex is greater than text.length() or not greater than startIndex
+     *
      * @see #classifyText(CharSequence, int, int, TextClassification.Options)
      */
-    // TODO: Consider deprecating (b/68846316)
+    @WorkerThread
+    @NonNull
+    default TextClassification classifyText(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int startIndex,
+            @IntRange(from = 0) int endIndex) {
+        return classifyText(text, startIndex, endIndex, (TextClassification.Options) null);
+    }
+
+    /**
+     * See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or
+     * {@link #classifyText(CharSequence, int, int)}.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
+     * calls this method, a stack overflow error will happen.
+     */
     @WorkerThread
     @NonNull
     default TextClassification classifyText(
@@ -129,7 +202,10 @@
             @IntRange(from = 0) int startIndex,
             @IntRange(from = 0) int endIndex,
             @Nullable LocaleList defaultLocales) {
-        return TextClassification.EMPTY;
+        final TextClassification.Options options = (defaultLocales != null)
+                ? new TextClassification.Options().setDefaultLocales(defaultLocales)
+                : null;
+        return classifyText(text, startIndex, endIndex, options);
     }
 
     /**
@@ -137,17 +213,39 @@
      * information.
      *
      * @param text the text to generate annotations for
-     * @param options configuration for link generation. If null, defaults will be used.
+     * @param options configuration for link generation
      *
      * @throws IllegalArgumentException if text is null
+     *
+     * @see #generateLinks(CharSequence)
      */
     @WorkerThread
     default TextLinks generateLinks(
             @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+        Utils.validateInput(text);
         return new TextLinks.Builder(text.toString()).build();
     }
 
     /**
+     * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
+     * information.
+     *
+     * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+     * {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
+     * a stack overflow error will happen.
+     *
+     * @param text the text to generate annotations for
+     *
+     * @throws IllegalArgumentException if text is null
+     *
+     * @see #generateLinks(CharSequence, TextLinks.Options)
+     */
+    @WorkerThread
+    default TextLinks generateLinks(@NonNull CharSequence text) {
+        return generateLinks(text, null);
+    }
+
+    /**
      * Logs a TextClassifier event.
      *
      * @param source the text classifier used to generate this event
@@ -164,4 +262,38 @@
     default TextClassifierConstants getSettings() {
         return TextClassifierConstants.DEFAULT;
     }
+
+
+    /**
+     * Utility functions for TextClassifier methods.
+     *
+     * <ul>
+     *  <li>Provides validation of input parameters to TextClassifier methods
+     * </ul>
+     *
+     * Intended to be used only in this package.
+     * @hide
+     */
+    final class Utils {
+
+        /**
+         * @throws IllegalArgumentException if text is null; startIndex is negative;
+         *      endIndex is greater than text.length() or is not greater than startIndex;
+         *      options is null
+         */
+        static void validateInput(
+                @NonNull CharSequence text, int startIndex, int endIndex) {
+            Preconditions.checkArgument(text != null);
+            Preconditions.checkArgument(startIndex >= 0);
+            Preconditions.checkArgument(endIndex <= text.length());
+            Preconditions.checkArgument(endIndex > startIndex);
+        }
+
+        /**
+         * @throws IllegalArgumentException if text is null or options is null
+         */
+        static void validateInput(@NonNull CharSequence text) {
+            Preconditions.checkArgument(text != null);
+        }
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 2ad6e02..df5e35f 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -90,8 +90,8 @@
     @Override
     public TextSelection suggestSelection(
             @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
-            @Nullable TextSelection.Options options) {
-        validateInput(text, selectionStartIndex, selectionEndIndex);
+            @NonNull TextSelection.Options options) {
+        Utils.validateInput(text, selectionStartIndex, selectionEndIndex);
         try {
             if (text.length() > 0) {
                 final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
@@ -141,18 +141,10 @@
     }
 
     @Override
-    public TextSelection suggestSelection(
-            @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
-            @Nullable LocaleList defaultLocales) {
-        return suggestSelection(text, selectionStartIndex, selectionEndIndex,
-                new TextSelection.Options().setDefaultLocales(defaultLocales));
-    }
-
-    @Override
     public TextClassification classifyText(
             @NonNull CharSequence text, int startIndex, int endIndex,
-            @Nullable TextClassification.Options options) {
-        validateInput(text, startIndex, endIndex);
+            @NonNull TextClassification.Options options) {
+        Utils.validateInput(text, startIndex, endIndex);
         try {
             if (text.length() > 0) {
                 final String string = text.toString();
@@ -176,17 +168,9 @@
     }
 
     @Override
-    public TextClassification classifyText(
-            @NonNull CharSequence text, int startIndex, int endIndex,
-            @Nullable LocaleList defaultLocales) {
-        return classifyText(text, startIndex, endIndex,
-                new TextClassification.Options().setDefaultLocales(defaultLocales));
-    }
-
-    @Override
     public TextLinks generateLinks(
-            @NonNull CharSequence text, @Nullable TextLinks.Options options) {
-        Preconditions.checkNotNull(text);
+            @NonNull CharSequence text, @NonNull TextLinks.Options options) {
+        Utils.validateInput(text);
         final String textString = text.toString();
         final TextLinks.Builder builder = new TextLinks.Builder(textString);
         try {
@@ -486,17 +470,6 @@
     }
 
     /**
-     * @throws IllegalArgumentException if text is null; startIndex is negative;
-     *      endIndex is greater than text.length() or is not greater than startIndex
-     */
-    private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) {
-        Preconditions.checkArgument(text != null);
-        Preconditions.checkArgument(startIndex >= 0);
-        Preconditions.checkArgument(endIndex <= text.length());
-        Preconditions.checkArgument(endIndex > startIndex);
-    }
-
-    /**
      * Creates intents based on the classification type.
      */
     private static final class IntentFactory {
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index f3cc827..76748d2 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -161,39 +161,28 @@
      * Optional input parameters for generating TextLinks.
      */
     public static final class Options {
-        private final LocaleList mLocaleList;
 
-        private Options(LocaleList localeList) {
-            this.mLocaleList = localeList;
+        private LocaleList mDefaultLocales;
+
+        /**
+         * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+         *      the provided text. If no locale preferences exist, set this to null or an empty
+         *      locale list.
+         */
+        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+            mDefaultLocales = defaultLocales;
+            return this;
         }
 
         /**
-         * Builder to construct Options.
+         * @return ordered list of locale preferences that can be used to disambiguate
+         *      the provided text.
          */
-        public static final class Builder {
-            private LocaleList mLocaleList;
-
-            /**
-             * Sets the LocaleList to use.
-             *
-             * @return this Builder.
-             */
-            public Builder setLocaleList(@Nullable LocaleList localeList) {
-                this.mLocaleList = localeList;
-                return this;
-            }
-
-            /**
-             * Builds the Options object.
-             */
-            public Options build() {
-                return new Options(mLocaleList);
-            }
+        @Nullable
+        public LocaleList getDefaultLocales() {
+            return mDefaultLocales;
         }
-        public @Nullable LocaleList getDefaultLocales() {
-            return mLocaleList;
-        }
-    };
+    }
 
     /**
      * A function to create spans from TextLinks.
@@ -204,13 +193,10 @@
      * @hide
      */
     public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
-            new Function<TextLink, ClickableSpan>() {
-        @Override
-        public ClickableSpan apply(TextLink textLink) {
-            // TODO: Implement.
-            throw new UnsupportedOperationException("Not yet implemented");
-        }
-    };
+            textLink -> {
+                // TODO: Implement.
+                throw new UnsupportedOperationException("Not yet implemented");
+            };
 
     /**
      * A builder to construct a TextLinks instance.
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 0a67954..480b27a 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -26,6 +26,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Information about where text selection should be.
@@ -114,8 +115,8 @@
 
     @Override
     public String toString() {
-        return String.format("TextSelection {%d, %d, %s}",
-                mStartIndex, mEndIndex, mEntityConfidence);
+        return String.format(Locale.US,
+                "TextSelection {%d, %d, %s}", mStartIndex, mEndIndex, mEntityConfidence);
     }
 
     /**
@@ -185,7 +186,7 @@
     }
 
     /**
-     * TextSelection optional input parameters.
+     * Optional input parameters for generating TextSelection.
      */
     public static final class Options {
 
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 665d694..6f99254 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2758,7 +2758,9 @@
      * <p>For example, an HTML form with 2 fields for username and password:
      *
      * <pre class="prettyprint">
+     *    &lt;label&gt;Username:&lt;/label&gt;
      *    &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
+     *    &lt;label&gt;Password:&lt;/label&gt;
      *    &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
      * </pre>
      *
@@ -2772,6 +2774,7 @@
      *     username.setHtmlInfo(username.newHtmlInfoBuilder("input")
      *         .addAttribute("type", "text")
      *         .addAttribute("name", "username")
+     *         .addAttribute("label", "Username:")
      *         .build());
      *     username.setHint("Email or username");
      *     username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
@@ -2785,6 +2788,7 @@
      *     password.setHtmlInfo(password.newHtmlInfoBuilder("input")
      *         .addAttribute("type", "password")
      *         .addAttribute("name", "password")
+     *         .addAttribute("label", "Password:")
      *         .build());
      *     password.setHint("Password");
      *     password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 517ad07..46c3983 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -364,13 +364,13 @@
     }
 
     /**
-     * Notify the host application to handle a SSL client certificate
-     * request. The host application is responsible for showing the UI
-     * if desired and providing the keys. There are three ways to
-     * respond: proceed(), cancel() or ignore(). Webview stores the response
-     * in memory (for the life of the application) if proceed() or cancel() is
-     * called and does not call {@code onReceivedClientCertRequest()} again for the
-     * same host and port pair. Webview does not store the response if ignore()
+     * Notify the host application to handle a SSL client certificate request. The host application
+     * is responsible for showing the UI if desired and providing the keys. There are three ways to
+     * respond: {@link ClientCertRequest#proceed}, {@link ClientCertRequest#cancel}, or {@link
+     * ClientCertRequest#ignore}. Webview stores the response in memory (for the life of the
+     * application) if {@link ClientCertRequest#proceed} or {@link ClientCertRequest#cancel} is
+     * called and does not call {@code onReceivedClientCertRequest()} again for the same host and
+     * port pair. Webview does not store the response if {@link ClientCertRequest#ignore}
      * is called. Note that, multiple layers in chromium network stack might be
      * caching the responses, so the behavior for ignore is only a best case
      * effort.
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 4d3189e..b379280 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -1952,7 +1952,8 @@
             CharSequence beforeText = mInputText.getText();
             if (!text.equals(beforeText.toString())) {
                 mInputText.setText(text);
-                if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+                        AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
                     mInputText.onInitializeAccessibilityEvent(event);
@@ -2612,7 +2613,7 @@
         }
 
         private void sendAccessibilityEventForVirtualText(int eventType) {
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
                 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
                 mInputText.onInitializeAccessibilityEvent(event);
                 mInputText.onPopulateAccessibilityEvent(event);
@@ -2623,7 +2624,7 @@
 
         private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
                 String text) {
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
                 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
                 event.setClassName(Button.class.getName());
                 event.setPackageName(mContext.getPackageName());
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d9bc51f..71532a7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10836,6 +10836,10 @@
 
     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
             int fromIndex, int removedCount, int addedCount) {
+        if (!AccessibilityManager.getInstance(mContext).isObservedEventType(
+                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) {
+            return;
+        }
         AccessibilityEvent event =
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
         event.setFromIndex(fromIndex);
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index d807120..bfde6ac 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -504,7 +504,8 @@
         private void trySendAccessibilityEvent() {
             AccessibilityManager accessibilityManager =
                     AccessibilityManager.getInstance(mView.getContext());
-            if (!accessibilityManager.isEnabled()) {
+            if (!accessibilityManager.isObservedEventType(
+                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
                 return;
             }
             // treat toasts as notifications since they are used to
diff --git a/core/java/com/android/internal/content/ReferrerIntent.java b/core/java/com/android/internal/content/ReferrerIntent.java
index 8d9a1cf..76dcc9b 100644
--- a/core/java/com/android/internal/content/ReferrerIntent.java
+++ b/core/java/com/android/internal/content/ReferrerIntent.java
@@ -19,6 +19,8 @@
 import android.content.Intent;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * Subclass of Intent that also contains referrer (as a package name) information.
  */
@@ -48,4 +50,21 @@
             return new ReferrerIntent[size];
         }
     };
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof ReferrerIntent)) {
+            return false;
+        }
+        final ReferrerIntent other = (ReferrerIntent) obj;
+        return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + filterHashCode();
+        result = 31 * result + Objects.hashCode(mReferrer);
+        return result;
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6ede72d..56d0bb2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6020,6 +6020,11 @@
         }
 
         @Override
+        public Timer getMulticastWakelockStats() {
+            return mWifiMulticastTimer;
+        }
+
+        @Override
         public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() {
             return mSyncStats.getMap();
         }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index bab0306aa..5ec9094 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -113,6 +113,11 @@
     void showGlobalActionsMenu();
 
     /**
+     * Notifies the status bar that a new rotation suggestion is available.
+     */
+    void onProposedRotationChanged(int rotation);
+
+    /**
      * Set whether the top app currently hides the statusbar.
      *
      * @param hidesStatusBar whether it is being hidden
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index c22be2c..9a6e542 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -60,6 +60,25 @@
         mBuffer[indexOf(mCursor++)] = t;
     }
 
+    /**
+     * Returns object of type <T> at the next writable slot, creating one if it is not already
+     * available. In case of any errors while creating the object, <code>null</code> will
+     * be returned.
+     */
+    public T getNextSlot() {
+        final int nextSlotIdx = indexOf(mCursor++);
+        T item = mBuffer[nextSlotIdx];
+        if (item == null) {
+            try {
+                item = (T) mBuffer.getClass().getComponentType().newInstance();
+            } catch (IllegalAccessException | InstantiationException e) {
+                return null;
+            }
+            mBuffer[nextSlotIdx] = item;
+        }
+        return item;
+    }
+
     public T[] toArray() {
         // Only generic way to create a T[] from another T[]
         T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index daf745f..bfe4323 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -61,17 +61,19 @@
      * Returns a default user icon for the given user.
      *
      * Note that for guest users, you should pass in {@code UserHandle.USER_NULL}.
+     *
+     * @param resources resources object to fetch user icon / color.
      * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon
      * @param light whether we want a light icon (suitable for a dark background)
      */
-    public static Drawable getDefaultUserIcon(int userId, boolean light) {
+    public static Drawable getDefaultUserIcon(Resources resources, int userId, boolean light) {
         int colorResId = light ? R.color.user_icon_default_white : R.color.user_icon_default_gray;
         if (userId != UserHandle.USER_NULL) {
             // Return colored icon instead
             colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
         }
-        Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate();
-        icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN);
+        Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
+        icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
         icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
         return icon;
     }
diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java
index d38ea2c..24f0b0c 100644
--- a/core/java/com/android/internal/view/TooltipPopup.java
+++ b/core/java/com/android/internal/view/TooltipPopup.java
@@ -142,7 +142,7 @@
         mTmpAnchorPos[1] -= mTmpAppPos[1];
         // mTmpAnchorPos is now relative to the main app window.
 
-        outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+        outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
 
         final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
         mContentView.measure(spec, spec);
@@ -157,6 +157,9 @@
                 outParams.y = yBelow;
             }
         } else {
+            // Use mTmpDisplayFrame.height() as the lower boundary instead of appView.getHeight(),
+            // as the latter includes the navigation bar, and tooltips do not look good over
+            // the navigation bar.
             if (yBelow + tooltipHeight <= mTmpDisplayFrame.height()) {
                 outParams.y = yBelow;
             } else {
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
index 50ad547..759a41a 100644
--- a/core/java/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -186,6 +186,9 @@
         }
 
         final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+        if (event == null) {
+            return false;
+        }
         return parent.requestSendAccessibilityEvent(mView, event);
     }
 
@@ -240,6 +243,9 @@
             if (parent != null) {
                 final AccessibilityEvent event = createEvent(virtualViewId,
                         AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+                if (event == null) {
+                    return;
+                }
                 event.setContentChangeTypes(changeTypes);
                 parent.requestSendAccessibilityEvent(mView, event);
             }
@@ -305,6 +311,9 @@
      *         the specified item.
      */
     private AccessibilityEvent createEventForHost(int eventType) {
+        if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+            return null;
+        }
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         mView.onInitializeAccessibilityEvent(event);
 
@@ -325,6 +334,9 @@
      *         the specified item.
      */
     private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+        if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+            return null;
+        }
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         event.setEnabled(true);
         event.setClassName(DEFAULT_CLASS_NAME);
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 5990d7b..2e8c27a 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -130,8 +130,10 @@
     chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
     chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
 
-    scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth);
-    scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight);
+    // The max value for the divRange is one pixel less than the actual max to ensure that the size
+    // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
+    scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth - 1);
+    scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight - 1);
 }
 
 class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index d7300c4..601bd98 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -25,6 +25,7 @@
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
+#include "scoped_nullable_primitive_array.h"
 #include <cstdint>
 #include <vector>
 #include <list>
@@ -87,9 +88,8 @@
 static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
                         jfloatArray recycleWidths, jfloatArray recycleAscents,
                         jfloatArray recycleDescents, jintArray recycleFlags,
-                        jint recycleLength, size_t nBreaks, const jint* breaks,
-                        const jfloat* widths, const jfloat* ascents, const jfloat* descents,
-                        const jint* flags) {
+                        jint recycleLength, const minikin::LineBreakResult& result) {
+    const size_t nBreaks = result.breakPoints.size();
     if ((size_t)recycleLength < nBreaks) {
         // have to reallocate buffers
         recycleBreaks = env->NewIntArray(nBreaks);
@@ -105,11 +105,11 @@
         env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags);
     }
     // copy data
-    env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, breaks);
-    env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, widths);
-    env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, ascents);
-    env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, descents);
-    env->SetIntArrayRegion(recycleFlags, 0, nBreaks, flags);
+    env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, result.breakPoints.data());
+    env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, result.widths.data());
+    env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, result.ascents.data());
+    env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, result.descents.data());
+    env->SetIntArrayRegion(recycleFlags, 0, nBreaks, result.flags.data());
 }
 
 static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
@@ -136,34 +136,22 @@
     minikin::android::StaticLayoutNative* builder = toNative(nativePtr);
 
     ScopedCharArrayRO text(env, javaText);
+    ScopedNullableIntArrayRO tabStops(env, variableTabStops);
 
-    // TODO: Reorganize minikin APIs.
-    minikin::LineBreaker b(minikin::U16StringPiece(text.get(), length));
-    if (variableTabStops == nullptr) {
-        b.setTabStops(nullptr, 0, defaultTabStop);
-    } else {
-        ScopedIntArrayRO stops(env, variableTabStops);
-        b.setTabStops(stops.get(), stops.size(), defaultTabStop);
-    }
-    b.setStrategy(builder->getStrategy());
-    b.setHyphenationFrequency(builder->getFrequency());
-    b.setJustified(builder->isJustified());
-    b.setLineWidthDelegate(builder->buildLineWidthDelegate(
-            firstWidth, firstWidthLineCount, restWidth, indentsOffset));
-
-    builder->addRuns(&b);
-
-    size_t nBreaks = b.computeBreaks();
+    minikin::U16StringPiece u16Text(text.get(), length);
+    minikin::MeasuredText measuredText = builder->measureText(u16Text);
+    minikin::LineBreakResult result = builder->computeBreaks(
+            u16Text, measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset,
+            tabStops.get(), tabStops.size(), defaultTabStop);
 
     recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
-            recycleFlags, recycleLength, nBreaks, b.getBreaks(), b.getWidths(), b.getAscents(),
-            b.getDescents(), b.getFlags());
+            recycleFlags, recycleLength, result);
 
-    env->SetFloatArrayRegion(charWidths, 0, b.size(), b.charWidths());
+    env->SetFloatArrayRegion(charWidths, 0, measuredText.widths.size(), measuredText.widths.data());
 
     builder->clearRuns();
 
-    return static_cast<jint>(nBreaks);
+    return static_cast<jint>(result.breakPoints.size());
 }
 
 // Basically similar to Paint.getTextRunAdvances but with C++ interface
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index bb1bfad..f77e6c4 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -291,7 +291,7 @@
 }
 
 static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
-        jobject surfaceObj, int rotation) {
+        jobject surfaceObj, jobject sourceCropObj, jfloat frameScale) {
 
     sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
     if (layerHandle == NULL) {
@@ -303,7 +303,42 @@
         return;
     }
 
-    ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), rotation);
+    Rect sourceCrop;
+    if (sourceCropObj != NULL) {
+        sourceCrop = rectFromObj(env, sourceCropObj);
+    }
+
+    ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), sourceCrop,
+            frameScale);
+}
+
+static jobject nativeCaptureLayersToBuffer(JNIEnv* env, jclass clazz, jobject layerHandleToken,
+        jobject sourceCropObj, jfloat frameScale) {
+
+    sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
+    if (layerHandle == NULL) {
+        return NULL;
+    }
+
+    Rect sourceCrop;
+    if (sourceCropObj != NULL) {
+        sourceCrop = rectFromObj(env, sourceCropObj);
+    }
+
+    sp<GraphicBuffer> buffer;
+    status_t res = ScreenshotClient::captureLayersToBuffer(layerHandle, sourceCrop, frameScale,
+            &buffer);
+    if (res != NO_ERROR) {
+        return NULL;
+    }
+
+    return env->CallStaticObjectMethod(gGraphicBufferClassInfo.clazz,
+                                       gGraphicBufferClassInfo.builder,
+                                       buffer->getWidth(),
+                                       buffer->getHeight(),
+                                       buffer->getPixelFormat(),
+                                       (jint)buffer->getUsage(),
+                                       (jlong)buffer.get());
 }
 
 static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
@@ -975,8 +1010,10 @@
     {"nativeScreenshotToBuffer",
      "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
      (void*)nativeScreenshotToBuffer },
-    {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;I)V",
+    {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;F)V",
             (void*)nativeCaptureLayers },
+    {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
+            (void*)nativeCaptureLayersToBuffer },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 32ef3dc..1407ae4 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -776,7 +776,10 @@
       }
 
       // Assign system_server to the correct memory cgroup.
-      if (!WriteStringToFile(StringPrintf("%d", pid), "/dev/memcg/system/tasks")) {
+      // Not all devices mount /dev/memcg so check for the file first
+      // to avoid unnecessarily printing errors and denials in the logs.
+      if (!access("/dev/memcg/system/tasks", F_OK) &&
+                !WriteStringToFile(StringPrintf("%d", pid), "/dev/memcg/system/tasks")) {
         ALOGE("couldn't write %d to /dev/memcg/system/tasks", pid);
       }
   }
diff --git a/core/jni/scoped_nullable_primitive_array.h b/core/jni/scoped_nullable_primitive_array.h
new file mode 100644
index 0000000..77f4c9d
--- /dev/null
+++ b/core/jni/scoped_nullable_primitive_array.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCOPED_NULLABLE_PRIMITIVE_ARRAY_H
+#define SCOPED_NULLABLE_PRIMITIVE_ARRAY_H
+
+#include <jni.h>
+
+namespace android {
+
+#define ARRAY_TRAITS(ARRAY_TYPE, POINTER_TYPE, NAME)                                  \
+class NAME ## ArrayTraits {                                                           \
+public:                                                                               \
+    static constexpr void getArrayRegion(JNIEnv* env, ARRAY_TYPE array, size_t start, \
+                                         size_t len, POINTER_TYPE out) {              \
+        env->Get ## NAME ## ArrayRegion(array, start, len, out);                      \
+    }                                                                                 \
+                                                                                      \
+    static constexpr POINTER_TYPE getArrayElements(JNIEnv* env, ARRAY_TYPE array) {   \
+        return env->Get ## NAME ## ArrayElements(array, nullptr);                     \
+    }                                                                                 \
+                                                                                      \
+    static constexpr void releaseArrayElements(JNIEnv* env, ARRAY_TYPE array,         \
+                                               POINTER_TYPE buffer, jint mode) {      \
+        env->Release ## NAME ## ArrayElements(array, buffer, mode);                   \
+    }                                                                                 \
+};                                                                                    \
+
+ARRAY_TRAITS(jbooleanArray, jboolean*, Boolean)
+ARRAY_TRAITS(jbyteArray, jbyte*, Byte)
+ARRAY_TRAITS(jcharArray, jchar*, Char)
+ARRAY_TRAITS(jdoubleArray, jdouble*, Double)
+ARRAY_TRAITS(jfloatArray, jfloat*, Float)
+ARRAY_TRAITS(jintArray, jint*, Int)
+ARRAY_TRAITS(jlongArray, jlong*, Long)
+ARRAY_TRAITS(jshortArray, jshort*, Short)
+
+#undef ARRAY_TRAITS
+
+template<typename JavaArrayType, typename PrimitiveType, class Traits, size_t preallocSize = 10>
+class ScopedArrayRO {
+public:
+    ScopedArrayRO(JNIEnv* env, JavaArrayType javaArray) : mEnv(env), mJavaArray(javaArray) {
+        if (mJavaArray == nullptr) {
+            mSize = 0;
+            mRawArray = nullptr;
+        } else {
+            mSize = mEnv->GetArrayLength(mJavaArray);
+            if (mSize <= preallocSize) {
+                Traits::getArrayRegion(mEnv, mJavaArray, 0, mSize, mBuffer);
+                mRawArray = mBuffer;
+            } else {
+                mRawArray = Traits::getArrayElements(mEnv, mJavaArray);
+            }
+        }
+    }
+
+    ~ScopedArrayRO() {
+        if (mRawArray != nullptr && mRawArray != mBuffer) {
+            Traits::releaseArrayElements(mEnv, mJavaArray, mRawArray, JNI_ABORT);
+        }
+    }
+
+    const PrimitiveType* get() const { return mRawArray; }
+    const PrimitiveType& operator[](size_t n) const { return mRawArray[n]; }
+    size_t size() const { return mSize; }
+
+private:
+    JNIEnv* const mEnv;
+    JavaArrayType mJavaArray;
+    PrimitiveType* mRawArray;
+    size_t mSize;
+    PrimitiveType mBuffer[preallocSize];
+    DISALLOW_COPY_AND_ASSIGN(ScopedArrayRO);
+};
+
+// ScopedNullable***ArrayRO provide convenient read-only access to Java array from JNI code.
+// These accept nullptr. In that case, get() returns nullptr and size() returns 0.
+using ScopedNullableBooleanArrayRO = ScopedArrayRO<jbooleanArray, jboolean, BooleanArrayTraits>;
+using ScopedNullableByteArrayRO = ScopedArrayRO<jbyteArray, jbyte, ByteArrayTraits>;
+using ScopedNullableCharArrayRO = ScopedArrayRO<jcharArray, jchar, CharArrayTraits>;
+using ScopedNullableDoubleArrayRO = ScopedArrayRO<jdoubleArray, jdouble, DoubleArrayTraits>;
+using ScopedNullableFloatArrayRO = ScopedArrayRO<jfloatArray, jfloat, FloatArrayTraits>;
+using ScopedNullableIntArrayRO = ScopedArrayRO<jintArray, jint, IntArrayTraits>;
+using ScopedNullableLongArrayRO = ScopedArrayRO<jlongArray, jlong, LongArrayTraits>;
+using ScopedNullableShortArrayRO = ScopedArrayRO<jshortArray, jshort, ShortArrayTraits>;
+
+}  // namespace android
+
+#endif  // SCOPED_NULLABLE_PRIMITIVE_ARRAY_H
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 0a3344f..cff1879 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -395,6 +395,12 @@
   };
   repeated WakeupReason wakeup_reason = 22;
 
+  message WifiMulticastWakelockTotal {
+    optional int64 duration_ms = 1;
+    optional int32 count = 2;
+  }
+  optional WifiMulticastWakelockTotal wifi_multicast_wakelock_total = 23;
+
   message WifiSignalStrength {
     enum Name {
       NONE = 0;
@@ -406,7 +412,7 @@
     optional Name name = 1;
     optional TimerProto total = 2;
   };
-  repeated WifiSignalStrength wifi_signal_strength = 23;
+  repeated WifiSignalStrength wifi_signal_strength = 24;
 
   message WifiState {
     enum Name {
@@ -422,7 +428,7 @@
     optional Name name = 1;
     optional TimerProto total = 2;
   };
-  repeated WifiState wifi_state = 24;
+  repeated WifiState wifi_state = 25;
 
   message WifiSupplicantState {
     enum Name {
@@ -443,7 +449,7 @@
     optional Name name = 1;
     optional TimerProto total = 2;
   };
-  repeated WifiSupplicantState wifi_supplicant_state = 25;
+  repeated WifiSupplicantState wifi_supplicant_state = 26;
 }
 
 message TimerProto {
@@ -775,4 +781,12 @@
     optional TimerProto background_scan = 4;
   };
   optional Wifi wifi = 27;
+
+  // WiFi Multicast Wakelock
+  // This timer tracks the duration and count for the app to request the
+  // wakelock for wifi multicast traffic.
+  // This wakelock disables the filtering of multicast packets to reach the host
+  // processor, and results in a power penalty.
+  // It is useful to monitor the applications resulting in that
+  optional TimerProto wifi_multicast_wakelock = 28;
 }
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index 5d5aea2..3229140 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -281,6 +281,7 @@
     optional SettingProto bluetooth_pbap_client_priority_prefix = 209;
     optional SettingProto bluetooth_sap_priority_prefix = 210;
     optional SettingProto bluetooth_pan_priority_prefix = 211;
+    optional SettingProto bluetooth_hearing_aid_priority_prefix = 345;
     optional SettingProto activity_manager_constants = 317;
     optional SettingProto device_idle_constants = 212;
     optional SettingProto device_idle_constants_watch = 213;
@@ -387,7 +388,7 @@
     optional SettingProto enable_deletion_helper_no_threshold_toggle = 340;
     optional SettingProto notification_snooze_options = 341;
 
-    // Next tag = 345;
+    // Next tag = 346;
 }
 
 message SecureSettingsProto {
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index d2cd190..d724437 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -20,6 +20,7 @@
 import "frameworks/base/core/proto/android/app/pendingintent.proto";
 import "frameworks/base/core/proto/android/internal/locallog.proto";
 import "frameworks/base/core/proto/android/os/worksource.proto";
+import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
 
 package com.android.server;
 
@@ -32,10 +33,9 @@
   optional int64 last_time_change_realtime = 4;
   // Current settings
   optional ConstantsProto settings = 5;
-  // UIDs currently in the foreground.
-  repeated int32 foreground_uids = 6;
-  // Packages forced into app standby.
-  repeated string forced_app_standby_packages = 7;
+
+  // Dump from ForceAppStandbyTracker.
+  optional ForceAppStandbyTrackerProto force_app_standby_tracker = 6;
 
   optional bool is_interactive = 8;
   // Only valid if is_interactive is false.
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
new file mode 100644
index 0000000..8753bf7
--- /dev/null
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.server;
+
+option java_multiple_files = true;
+
+// Dump from ForceAppStandbyTracker.
+message ForceAppStandbyTrackerProto {
+  // Whether all apps are forced standby or not.
+  optional bool force_all_apps_standby = 1;
+
+  // UIDs currently in the foreground.
+  repeated int32 foreground_uids = 2;
+
+  // App ids that are in power-save whitelist.
+  repeated int32 power_save_whitelist_app_ids = 3;
+
+  // App ids that are in temporary power-save whitelist.
+  repeated int32 temp_power_save_whitelist_app_ids = 4;
+
+  message RunAnyInBackgroundRestrictedPackages {
+    optional int32 uid = 1;
+    optional string package_name = 2;
+  }
+
+  // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND.
+  repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5;
+}
diff --git a/core/res/res/drawable/btn_colored_material.xml b/core/res/res/drawable/btn_colored_material.xml
index c3c5760..7ba21e8 100644
--- a/core/res/res/drawable/btn_colored_material.xml
+++ b/core/res/res/drawable/btn_colored_material.xml
@@ -23,7 +23,7 @@
         <item>
             <shape android:shape="rectangle"
                    android:tint="@color/btn_colored_background_material">
-                <corners android:radius="@dimen/control_corner_material" />
+                <corners android:radius="?attr/buttonCornerRadius" />
                 <solid android:color="@color/white" />
                 <padding android:left="@dimen/button_padding_horizontal_material"
                          android:top="@dimen/button_padding_vertical_material"
diff --git a/core/res/res/drawable/btn_default_mtrl_shape.xml b/core/res/res/drawable/btn_default_mtrl_shape.xml
index 8a31d5e..9d9cd06 100644
--- a/core/res/res/drawable/btn_default_mtrl_shape.xml
+++ b/core/res/res/drawable/btn_default_mtrl_shape.xml
@@ -23,7 +23,7 @@
        android:insetBottom="@dimen/button_inset_vertical_material">
     <shape android:shape="rectangle"
            android:tint="?attr/colorButtonNormal">
-        <corners android:radius="@dimen/control_corner_material" />
+        <corners android:radius="?attr/buttonCornerRadius" />
         <solid android:color="@color/white" />
         <padding android:left="@dimen/button_padding_horizontal_material"
                  android:top="@dimen/button_padding_vertical_material"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index caeb43a..49b0ee7 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -23,7 +23,8 @@
             android:id="@+id/actions"
             android:layout_width="match_parent"
             android:layout_height="@dimen/notification_action_list_height"
-            android:paddingEnd="4dp"
+            android:paddingEnd="12dp"
+            android:paddingStart="8dp"
             android:orientation="horizontal"
             android:gravity="center_vertical"
             android:visibility="gone"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0eefec9..da8a9cc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1016,6 +1016,9 @@
         <!-- Style for the "neutral" buttons within button bars. -->
         <attr name="buttonBarNeutralButtonStyle" format="reference" />
 
+        <!-- Corner radius of buttons. -->
+        <attr name="buttonCornerRadius" format="dimension" />
+
         <!-- Style for the search query widget. -->
         <attr name="searchViewStyle" format="reference" />
 
@@ -3021,6 +3024,12 @@
         <!-- Whether this View should use a default focus highlight when it gets focused but
              doesn't have {@link android.R.attr#state_focused} defined in its background. -->
         <attr name="defaultFocusHighlightEnabled" format="boolean" />
+
+        <!-- Whether this view should be treated as a focusable unit by screen reader accessibility
+             tools. See {@link android.view.View#setScreenReaderFocusable(boolean)}. The default
+             value, {@code false}, leaves the screen reader to consider other signals, such as
+             focusability or the presence of text, to decide what it focus.-->
+        <attr name="screenReaderFocusable" format="boolean" />
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c104616..171f74f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1955,7 +1955,7 @@
     <string name="config_dozeLongPressSensorType" translatable="false"></string>
 
     <!-- Control whether the always on display mode is available. This should only be enabled on
-         devices where the display has be tuned to be power efficient in DOZE and/or DOZE_SUSPEND
+         devices where the display has been tuned to be power efficient in DOZE and/or DOZE_SUSPEND
          states. -->
     <bool name="config_dozeAlwaysOnDisplayAvailable">false</bool>
 
@@ -3174,6 +3174,12 @@
 
     <!-- Corner radius of system dialogs -->
     <dimen name="config_dialogCornerRadius">2dp</dimen>
+    <!-- Corner radius of system buttons -->
+    <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen>
+    <!-- Controls whether system buttons use all caps for text -->
+    <bool name="config_buttonTextAllCaps">true</bool>
+    <!-- Name of the font family used for system buttons -->
+    <string name="config_fontFamilyButton">@string/font_family_button_material</string>
 
     <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 946216c..a659b37 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -152,11 +152,11 @@
     <dimen name="dialog_padding">16dp</dimen>
 
     <!-- The margin on the start of the content view -->
-    <dimen name="notification_content_margin_start">16dp</dimen>
+    <dimen name="notification_content_margin_start">24dp</dimen>
 
     <!-- The margin on the end of the content view
         Keep in sync with notification_content_plus_picture_margin! -->
-    <dimen name="notification_content_margin_end">16dp</dimen>
+    <dimen name="notification_content_margin_end">24dp</dimen>
 
     <!-- The margin on the end of the content view with a picture.
         Keep in sync with notification_content_plus_picture_margin! -->
@@ -166,7 +166,7 @@
         content end margin.
         Keep equal to (notification_content_picture_margin + notification_content_margin_end)!
     -->
-    <dimen name="notification_content_plus_picture_margin_end">72dp</dimen>
+    <dimen name="notification_content_plus_picture_margin_end">80dp</dimen>
 
     <!-- The additional margin on the sides of the ambient view. -->
     <dimen name="notification_extra_margin_ambient">16dp</dimen>
@@ -175,10 +175,10 @@
     <dimen name="notification_action_list_height">56dp</dimen>
 
     <!-- height of the content margin to accomodate for the header -->
-    <dimen name="notification_content_margin_top">37.5dp</dimen>
+    <dimen name="notification_content_margin_top">41.5dp</dimen>
 
     <!-- height of the content margin on the bottom -->
-    <dimen name="notification_content_margin_bottom">16dp</dimen>
+    <dimen name="notification_content_margin_bottom">18dp</dimen>
 
     <!-- The height of the progress bar. -->
     <dimen name="notification_progress_bar_height">15dp</dimen>
@@ -187,16 +187,16 @@
     <dimen name="notification_progress_margin_top">8dp</dimen>
 
     <!-- height of the notification header (for icon and package name) -->
-    <dimen name="notification_header_height">48dp</dimen>
+    <dimen name="notification_header_height">54dp</dimen>
 
     <!-- The height of the background for a notification header on a group -->
-    <dimen name="notification_header_background_height">45.5dp</dimen>
+    <dimen name="notification_header_background_height">49.5dp</dimen>
 
     <!-- The top padding for the notification header -->
-    <dimen name="notification_header_padding_top">10dp</dimen>
+    <dimen name="notification_header_padding_top">14dp</dimen>
 
     <!-- The bottom padding for the notification header -->
-    <dimen name="notification_header_padding_bottom">11dp</dimen>
+    <dimen name="notification_header_padding_bottom">13dp</dimen>
 
     <!-- The margin at the bottom of the notification header. -->
     <dimen name="notification_header_margin_bottom">5dp</dimen>
@@ -266,6 +266,8 @@
     <dimen name="alert_dialog_round_padding">27dip</dimen>
     <!-- Dialog title height -->
     <dimen name="alert_dialog_title_height">64dip</dimen>
+    <!-- Dialog button bar width -->
+    <dimen name="alert_dialog_button_bar_width">64dp</dimen>
     <!-- Dialog button bar height -->
     <dimen name="alert_dialog_button_bar_height">48dip</dimen>
     <!-- Leanback dialog vertical margin -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d3533fe..3dbb89c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2852,9 +2852,13 @@
       <public name="compileSdkVersion" />
       <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
       <public name="compileSdkVersionCodename" />
+      <public name="screenReaderFocusable" />
+      <public name="buttonCornerRadius" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
+      <public name="Widget.DeviceDefault.Button.Colored" />
+      <public name="Widget.DeviceDefault.Button.Borderless.Colored" />
     </public-group>
 
     <public-group type="id" first-id="0x01020044">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e4310e1..2f1d679 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2853,7 +2853,7 @@
     <string name="unsupported_display_size_show">Always show</string>
 
     <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. -->
-    <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
+    <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for an incompatible version of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
     <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. -->
     <string name="unsupported_compile_sdk_show">Always show</string>
     <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. -->
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 93a5264..189b3b7 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -37,6 +37,10 @@
     <style name="Widget.DeviceDefault.Button.Small" parent="Widget.Material.Button.Small"/>
     <style name="Widget.DeviceDefault.Button.Inset" parent="Widget.Material.Button.Inset"/>
     <style name="Widget.DeviceDefault.Button.Toggle" parent="Widget.Material.Button.Toggle"/>
+    <style name="Widget.DeviceDefault.Button.Colored" parent="Widget.Material.Button.Colored">
+        <item name="textAppearance">?attr/textAppearanceButton</item>
+        <item name="textColor">@color/btn_colored_text_material</item>
+    </style>
     <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView"/>
     <style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/>
     <style name="Widget.DeviceDefault.AutoCompleteTextView" parent="Widget.Material.AutoCompleteTextView"/>
@@ -77,6 +81,15 @@
     <style name="Widget.DeviceDefault.ActionButton.CloseMode" parent="Widget.Material.ActionButton.CloseMode"/>
     <style name="Widget.DeviceDefault.ActionBar" parent="Widget.Material.ActionBar"/>
     <style name="Widget.DeviceDefault.Button.Borderless" parent="Widget.Material.Button.Borderless"/>
+    <!-- Colored borderless ink button -->
+    <style name="Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored</item>
+    </style>
+    <!-- Alert dialog button bar button -->
+    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog" parent="Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="minWidth">@dimen/alert_dialog_button_bar_width</item>
+        <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
+    </style>
     <style name="Widget.DeviceDefault.Tab" parent="Widget.Material.Tab"/>
     <style name="Widget.DeviceDefault.CalendarView" parent="Widget.Material.CalendarView"/>
     <style name="Widget.DeviceDefault.DatePicker" parent="Widget.Material.DatePicker"/>
@@ -211,7 +224,10 @@
     <style name="TextAppearance.DeviceDefault.SearchResult.Title" parent="TextAppearance.Material.SearchResult.Title"/>
     <style name="TextAppearance.DeviceDefault.SearchResult.Subtitle" parent="TextAppearance.Material.SearchResult.Subtitle"/>
     <style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget"/>
-    <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button"/>
+    <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button">
+        <item name="fontFamily">@string/config_fontFamilyButton</item>
+        <item name="textAllCaps">@bool/config_buttonTextAllCaps</item>
+    </style>
     <style name="TextAppearance.DeviceDefault.Widget.IconMenu.Item" parent="TextAppearance.Material.Widget.IconMenu.Item"/>
     <style name="TextAppearance.DeviceDefault.Widget.TabWidget" parent="TextAppearance.Material.Widget.TabWidget"/>
     <style name="TextAppearance.DeviceDefault.Widget.TextView" parent="TextAppearance.Material.Widget.TextView"/>
@@ -220,6 +236,9 @@
     <style name="TextAppearance.DeviceDefault.Widget.DropDownItem" parent="TextAppearance.Material.Widget.DropDownItem"/>
     <style name="TextAppearance.DeviceDefault.Widget.TextView.SpinnerItem" parent="TextAppearance.Material.Widget.TextView.SpinnerItem"/>
     <style name="TextAppearance.DeviceDefault.Widget.EditText" parent="TextAppearance.Material.Widget.EditText"/>
+    <style name="TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored" parent="TextAppearance.DeviceDefault.Widget.Button">
+        <item name="textColor">@color/btn_colored_borderless_text_material</item>
+    </style>
     <style name="TextAppearance.DeviceDefault.Widget.PopupMenu" parent="TextAppearance.Material.Widget.PopupMenu"/>
     <style name="TextAppearance.DeviceDefault.Widget.PopupMenu.Large" parent="TextAppearance.Material.Widget.PopupMenu.Large"/>
     <style name="TextAppearance.DeviceDefault.Widget.PopupMenu.Small" parent="TextAppearance.Material.Widget.PopupMenu.Small"/>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 2cd4dcb..def650a 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -561,7 +561,7 @@
 
     <!-- Alert dialog button bar button -->
     <style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Colored">
-        <item name="minWidth">64dp</item>
+        <item name="minWidth">@dimen/alert_dialog_button_bar_width</item>
         <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
     </style>
 
@@ -1304,7 +1304,7 @@
         <item name="paddingBottom">@dimen/notification_header_padding_bottom</item>
         <item name="layout_marginBottom">@dimen/notification_header_margin_bottom</item>
         <item name="paddingStart">@dimen/notification_content_margin_start</item>
-        <item name="paddingEnd">16dp</item>
+        <item name="paddingEnd">@dimen/notification_content_margin_end</item>
         <item name="gravity">top</item>
     </style>
 
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 68d5523..39310a8 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -69,12 +69,13 @@
         <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.DeviceDefault.Widget.PopupMenu.Small</item>
 
         <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
         <item name="buttonStyle">@style/Widget.DeviceDefault.Button</item>
 
         <item name="buttonStyleSmall">@style/Widget.DeviceDefault.Button.Small</item>
         <item name="buttonStyleInset">@style/Widget.DeviceDefault.Button.Inset</item>
-
         <item name="buttonStyleToggle">@style/Widget.DeviceDefault.Button.Toggle</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
         <item name="switchStyle">@style/Widget.DeviceDefault.CompoundButton.Switch</item>
 
         <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless</item>
@@ -219,6 +220,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar.  This theme
@@ -232,6 +240,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and
@@ -247,6 +262,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent
@@ -261,6 +283,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for dialog windows and activities. This changes the window to be
@@ -273,8 +302,14 @@
         <item name="buttonBarStyle">@style/DeviceDefault.ButtonBar.AlertDialog</item>
         <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless.Small</item>
 
+        <!-- Text styles -->
         <item name="textAppearance">@style/TextAppearance.DeviceDefault</item>
         <item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item>
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
@@ -297,6 +332,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
@@ -309,6 +351,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
@@ -322,6 +371,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -351,6 +407,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for a window without an action bar that will be displayed either
@@ -365,6 +428,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for a presentation window on a secondary display. -->
@@ -377,6 +447,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for panel windows. This removes all extraneous window
@@ -391,6 +468,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -404,6 +488,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -417,6 +508,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault style for input methods, which is used by the
@@ -430,6 +528,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault style for input methods, which is used by the
@@ -443,6 +548,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -456,6 +568,13 @@
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar">
@@ -467,6 +586,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
@@ -478,6 +604,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style -->
@@ -501,6 +634,7 @@
         <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.DeviceDefault.Widget.PopupMenu.Small</item>
 
         <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
         <item name="buttonStyle">@style/Widget.DeviceDefault.Light.Button</item>
 
         <item name="buttonStyleSmall">@style/Widget.DeviceDefault.Light.Button.Small</item>
@@ -644,6 +778,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar -->
@@ -656,6 +797,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar.
@@ -669,6 +817,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar
@@ -684,6 +839,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent
@@ -698,6 +860,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
@@ -711,11 +880,15 @@
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
 
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
         <item name="buttonBarStyle">@style/DeviceDefault.Light.ButtonBar.AlertDialog</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
         <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Light.Button.Borderless.Small</item>
 
         <item name="textAppearance">@style/TextAppearance.DeviceDefault</item>
         <item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item>
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
@@ -734,6 +907,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
      <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar -->
@@ -746,6 +926,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum
@@ -759,6 +946,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -798,6 +992,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault light theme for a window without an action bar that will be displayed either
@@ -812,6 +1013,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault light theme for a presentation window on a secondary display. -->
@@ -824,6 +1032,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault light theme for panel windows. This removes all extraneous window
@@ -838,6 +1053,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
@@ -851,6 +1073,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.SearchBar" parent="Theme.Material.Light.SearchBar">
@@ -862,6 +1091,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
@@ -873,6 +1109,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- DeviceDefault theme for a window that should look like the Settings app.  -->
@@ -882,6 +1125,7 @@
         <item name="popupTheme">@style/ThemeOverlay.DeviceDefault.Popup.Light</item>
 
         <!-- Color palette -->
+        <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorPrimary">@color/primary_device_default_settings_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
@@ -896,6 +1140,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <!-- @hide DeviceDefault theme for a window that should use Settings theme colors
@@ -908,6 +1159,13 @@
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorControlNormal">?attr/textColorPrimary</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Light.Dialog">
@@ -930,6 +1188,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.Material.Settings.Dialog">
@@ -942,6 +1207,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.DialogWhenLarge" parent="Theme.Material.Settings.DialogWhenLarge">
@@ -954,6 +1226,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -966,6 +1245,13 @@
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index c317121..9e6b1ab 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -103,6 +103,7 @@
         <item name="buttonStyleSmall">@style/Widget.Material.Button.Small</item>
         <item name="buttonStyleInset">@style/Widget.Material.Button.Inset</item>
         <item name="buttonStyleToggle">@style/Widget.Material.Button.Toggle</item>
+        <item name="buttonCornerRadius">@dimen/control_corner_material</item>
 
         <item name="switchStyle">@style/Widget.Material.CompoundButton.Switch</item>
         <item name="mediaRouteButtonStyle">@style/Widget.Material.MediaRouteButton</item>
@@ -472,6 +473,7 @@
         <item name="buttonStyleSmall">@style/Widget.Material.Light.Button.Small</item>
         <item name="buttonStyleInset">@style/Widget.Material.Light.Button.Inset</item>
         <item name="buttonStyleToggle">@style/Widget.Material.Light.Button.Toggle</item>
+        <item name="buttonCornerRadius">@dimen/control_corner_material</item>
 
         <item name="switchStyle">@style/Widget.Material.Light.CompoundButton.Switch</item>
         <item name="mediaRouteButtonStyle">@style/Widget.Material.Light.MediaRouteButton</item>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 51bfc20..3f2a46a 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -68,6 +68,7 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SMS"/>
     <uses-permission android:name="android.permission.TEST_GRANTED" />
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 23d70b8..970a0f0 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Runs Frameworks Core Tests.">
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="FrameworksCoreTests.apk" />
+        <option name="test-file-name" value="BstatsTestApp.apk" />
     </target_preparer>
 
     <option name="test-suite-tag" value="apct" />
diff --git a/core/tests/coretests/BstatsTestApp/Android.mk b/core/tests/coretests/BstatsTestApp/Android.mk
new file mode 100644
index 0000000..6280257
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BstatsTestApp
+LOCAL_CERTIFICATE := platform
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..0cb5498
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.coretests.apps.bstatstestapp">
+
+    <application>
+        <activity android:name=".TestActivity"
+                  android:exported="true" />
+        <service android:name=".IsolatedTestService"
+                 android:exported="true"
+                 android:isolatedProcess="true" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
new file mode 100644
index 0000000..1f5f397
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class IsolatedTestService extends Service {
+    private static final String TAG = IsolatedTestService.class.getName();
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mReceiver.asBinder();
+    }
+
+    private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+        @Override
+        public void doSomeWork(int durationMs) {
+            final long endTime = SystemClock.uptimeMillis() + durationMs;
+            double x;
+            double y;
+            double z;
+            while (SystemClock.uptimeMillis() <= endTime) {
+                x = 0.02;
+                x *= 1000;
+                y = x % 5;
+                z = Math.sqrt(y / 100);
+            }
+        };
+
+        @Override
+        public void finishHost() {
+            stopSelf();
+        }
+    };
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
new file mode 100644
index 0000000..87b14d9
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+    private static final String TAG = TestActivity.class.getName();
+
+    private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.d(TAG, "onCreate called");
+        notifyActivityLaunched();
+    }
+
+    private void notifyActivityLaunched() {
+        if (getIntent() == null) {
+            return;
+        }
+
+        final Bundle extras = getIntent().getExtras();
+        if (extras == null) {
+            return;
+        }
+        final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+                extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+        try {
+            callback.onActivityLaunched(mReceiver.asBinder());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error occured while notifying the test: " + e);
+        }
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        Log.d(TAG, "finish called");
+    }
+
+    private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+        @Override
+        public void doSomeWork(int durationMs) {
+            final long endTime = SystemClock.uptimeMillis() + durationMs;
+            double x;
+            double y;
+            double z;
+            while (SystemClock.uptimeMillis() <= endTime) {
+                x = 0.02;
+                x *= 1000;
+                y = x % 5;
+                z = Math.sqrt(y / 100);
+            }
+        };
+
+        @Override
+        public void finishHost() {
+            finish();
+        }
+    };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
new file mode 100644
index 0000000..53a181a
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface ICmdCallback {
+    void onActivityLaunched(IBinder receiver);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
new file mode 100644
index 0000000..c406570
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface ICmdReceiver {
+    void doSomeWork(int durationMs);
+    void finishHost();
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
new file mode 100644
index 0000000..e0fcf08
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+// TODO(lifecycler): Add to presubmit after checking for flakiness.
+public class ClientTransactionTests {
+
+    @Test
+    public void testPrepare() {
+        ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+        ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+        ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class);
+        IBinder token = mock(IBinder.class);
+
+        ClientTransaction transaction = new ClientTransaction(null /* client */,
+                token /* activityToken */);
+        transaction.addCallback(callback1);
+        transaction.addCallback(callback2);
+        transaction.setLifecycleStateRequest(stateRequest);
+
+        transaction.prepare(clientTransactionHandler);
+
+        verify(callback1, times(1)).prepare(clientTransactionHandler, token);
+        verify(callback2, times(1)).prepare(clientTransactionHandler, token);
+        verify(stateRequest, times(1)).prepare(clientTransactionHandler, token);
+    }
+
+    @Test
+    public void testExecute() {
+        ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+        ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+        ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+        IBinder token = mock(IBinder.class);
+
+        ClientTransaction transaction = new ClientTransaction(null /* client */,
+                token /* activityToken */);
+        transaction.addCallback(callback1);
+        transaction.addCallback(callback2);
+        transaction.setLifecycleStateRequest(stateRequest);
+
+        ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class);
+        transaction.prepare(clientTransactionHandler);
+        transaction.execute(clientTransactionHandler);
+
+        verify(callback1, times(1)).execute(clientTransactionHandler, token);
+        verify(callback2, times(1)).execute(clientTransactionHandler, token);
+        verify(stateRequest, times(1)).execute(clientTransactionHandler, token);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
new file mode 100644
index 0000000..9db7550
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.IApplicationThread;
+import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** Test parcelling and unparcelling of transactions and transaction items. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+// TODO(lifecycler): Add to presubmit after checking for flakiness.
+public class TransactionParcelTests {
+
+    private Parcel mParcel;
+
+    @Before
+    public void setUp() throws Exception {
+        mParcel = Parcel.obtain();
+    }
+
+    @Test
+    public void testConfigurationChange() {
+        // Write to parcel
+        ConfigurationChangeItem item = new ConfigurationChangeItem(config());
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        ConfigurationChangeItem result = ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testActivityConfigChange() {
+        // Write to parcel
+        ActivityConfigurationChangeItem item = new ActivityConfigurationChangeItem(config());
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        ActivityConfigurationChangeItem result =
+                ActivityConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testMoveToDisplay() {
+        // Write to parcel
+        MoveToDisplayItem item = new MoveToDisplayItem(4 /* targetDisplayId */, config());
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testNewIntent() {
+        // Write to parcel
+        NewIntentItem item = new NewIntentItem(referrerIntentList(), true /* pause */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testActivityResult() {
+        // Write to parcel
+        ActivityResultItem item = new ActivityResultItem(resultInfoList());
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testPipModeChange() {
+        // Write to parcel
+        PipModeChangeItem item = new PipModeChangeItem(true /* isInPipMode */, config());
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        PipModeChangeItem result = PipModeChangeItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testMultiWindowModeChange() {
+        // Write to parcel
+        MultiWindowModeChangeItem item = new MultiWindowModeChangeItem(
+                true /* isInMultiWindowMode */, config());
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        MultiWindowModeChangeItem result =
+                MultiWindowModeChangeItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testWindowVisibilityChange() {
+        // Write to parcel
+        WindowVisibilityItem item = new WindowVisibilityItem(true /* showWindow */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+
+        // Check different value
+        item = new WindowVisibilityItem(false);
+
+        mParcel = Parcel.obtain();
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testDestroy() {
+        DestroyActivityItem item = new DestroyActivityItem(true /* finished */,
+                135 /* configChanges */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testLaunch() {
+        // Write to parcel
+        Intent intent = new Intent("action");
+        int ident = 57;
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.flags = 42;
+        activityInfo.maxAspectRatio = 2.4f;
+        activityInfo.launchToken = "token";
+        activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.packageName = "packageName";
+        activityInfo.name = "name";
+        Configuration overrideConfig = new Configuration();
+        overrideConfig.assetsSeq = 5;
+        CompatibilityInfo compat = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        String referrer = "referrer";
+        int procState = 4;
+        Bundle bundle = new Bundle();
+        bundle.putString("key", "value");
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putInt("k", 4);
+
+        LaunchActivityItem item = new LaunchActivityItem(intent, ident, activityInfo,
+                config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
+                procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
+                true /* notResumed */, true /* isForward */, null /* profilerInfo */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        LaunchActivityItem result = LaunchActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testPause() {
+        // Write to parcel
+        PauseActivityItem item = new PauseActivityItem(true /* finished */, true /* userLeaving */,
+                135 /* configChanges */, true /* dontReport */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        PauseActivityItem result = PauseActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testResume() {
+        // Write to parcel
+        ResumeActivityItem item = new ResumeActivityItem(27 /* procState */, true /* isForward */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testStop() {
+        // Write to parcel
+        StopActivityItem item = new StopActivityItem(true /* showWindow */, 14 /* configChanges */);
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        StopActivityItem result = StopActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertTrue(item.equals(result));
+    }
+
+    @Test
+    public void testClientTransaction() {
+        // Write to parcel
+        WindowVisibilityItem callback1 = new WindowVisibilityItem(true);
+        ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config());
+
+        StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */,
+                78 /* configChanges */);
+
+        IApplicationThread appThread = new StubAppThread();
+        Binder activityToken = new Binder();
+
+        ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+        transaction.addCallback(callback1);
+        transaction.addCallback(callback2);
+        transaction.setLifecycleStateRequest(lifecycleRequest);
+
+        writeAndPrepareForReading(transaction);
+
+        // Read from parcel and assert
+        ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(transaction.hashCode(), result.hashCode());
+        assertTrue(transaction.equals(result));
+    }
+
+    @Test
+    public void testClientTransactionCallbacksOnly() {
+        // Write to parcel
+        WindowVisibilityItem callback1 = new WindowVisibilityItem(true);
+        ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config());
+
+        IApplicationThread appThread = new StubAppThread();
+        Binder activityToken = new Binder();
+
+        ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+        transaction.addCallback(callback1);
+        transaction.addCallback(callback2);
+
+        writeAndPrepareForReading(transaction);
+
+        // Read from parcel and assert
+        ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(transaction.hashCode(), result.hashCode());
+        assertTrue(transaction.equals(result));
+    }
+
+    @Test
+    public void testClientTransactionLifecycleOnly() {
+        // Write to parcel
+        StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */,
+                78 /* configChanges */);
+
+        IApplicationThread appThread = new StubAppThread();
+        Binder activityToken = new Binder();
+
+        ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+        transaction.setLifecycleStateRequest(lifecycleRequest);
+
+        writeAndPrepareForReading(transaction);
+
+        // Read from parcel and assert
+        ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(transaction.hashCode(), result.hashCode());
+        assertTrue(transaction.equals(result));
+    }
+
+    private static List<ResultInfo> resultInfoList() {
+        String resultWho1 = "resultWho1";
+        int requestCode1 = 7;
+        int resultCode1 = 4;
+        Intent data1 = new Intent("action1");
+        ResultInfo resultInfo1 = new ResultInfo(resultWho1, requestCode1, resultCode1, data1);
+
+        String resultWho2 = "resultWho2";
+        int requestCode2 = 8;
+        int resultCode2 = 6;
+        Intent data2 = new Intent("action2");
+        ResultInfo resultInfo2 = new ResultInfo(resultWho2, requestCode2, resultCode2, data2);
+
+        List<ResultInfo> resultInfoList = new ArrayList<>();
+        resultInfoList.add(resultInfo1);
+        resultInfoList.add(resultInfo2);
+
+        return resultInfoList;
+    }
+
+    private static List<ReferrerIntent> referrerIntentList() {
+        Intent intent1 = new Intent("action1");
+        ReferrerIntent referrerIntent1 = new ReferrerIntent(intent1, "referrer1");
+
+        Intent intent2 = new Intent("action2");
+        ReferrerIntent referrerIntent2 = new ReferrerIntent(intent2, "referrer2");
+
+        List<ReferrerIntent> referrerIntents = new ArrayList<>();
+        referrerIntents.add(referrerIntent1);
+        referrerIntents.add(referrerIntent2);
+
+        return referrerIntents;
+    }
+
+    private static Configuration config() {
+        Configuration config = new Configuration();
+        config.densityDpi = 10;
+        config.fontScale = 0.3f;
+        config.screenHeightDp = 15;
+        config.orientation = ORIENTATION_LANDSCAPE;
+        return config;
+    }
+
+    /** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
+    private void writeAndPrepareForReading(Parcelable parcelable) {
+        parcelable.writeToParcel(mParcel, 0 /* flags */);
+        mParcel.setDataPosition(0);
+    }
+
+    /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */
+    class StubAppThread extends android.app.IApplicationThread.Stub  {
+
+        @Override
+        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleReceiver(Intent intent, ActivityInfo activityInfo,
+                CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b,
+                int i1, int i2) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo,
+                CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleStopService(IBinder iBinder) throws RemoteException {
+        }
+
+        @Override
+        public void bindApplication(String s, ApplicationInfo applicationInfo,
+                List<ProviderInfo> list, ComponentName componentName, ProfilerInfo profilerInfo,
+                Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
+                IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
+                boolean b2, boolean b3, Configuration configuration,
+                CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1)
+                throws RemoteException {
+        }
+
+        @Override
+        public void scheduleExit() throws RemoteException {
+        }
+
+        @Override
+        public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice)
+                throws RemoteException {
+        }
+
+        @Override
+        public void updateTimeZone() throws RemoteException {
+        }
+
+        @Override
+        public void processInBackground() throws RemoteException {
+        }
+
+        @Override
+        public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i)
+                throws RemoteException {
+        }
+
+        @Override
+        public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException {
+        }
+
+        @Override
+        public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
+                String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent,
+                int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2)
+                throws RemoteException {
+        }
+
+        @Override
+        public void scheduleLowMemory() throws RemoteException {
+        }
+
+        @Override
+        public void scheduleRelaunchActivity(IBinder iBinder, List<ResultInfo> list,
+                List<ReferrerIntent> list1, int i, boolean b, Configuration configuration,
+                Configuration configuration1, boolean b1) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleSleeping(IBinder iBinder, boolean b) throws RemoteException {
+        }
+
+        @Override
+        public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i)
+                throws RemoteException {
+        }
+
+        @Override
+        public void setSchedulingGroup(int i) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
+                CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo,
+                CompatibilityInfo compatibilityInfo) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle)
+                throws RemoteException {
+        }
+
+        @Override
+        public void scheduleSuicide() throws RemoteException {
+        }
+
+        @Override
+        public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleCrash(String s) throws RemoteException {
+        }
+
+        @Override
+        public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
+                String s, String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void clearDnsCache() throws RemoteException {
+        }
+
+        @Override
+        public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException {
+        }
+
+        @Override
+        public void setCoreSettings(Bundle bundle) throws RemoteException {
+        }
+
+        @Override
+        public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo)
+                throws RemoteException {
+        }
+
+        @Override
+        public void scheduleTrimMemory(int i) throws RemoteException {
+        }
+
+        @Override
+        public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor,
+                Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3,
+                boolean b4, String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
+                throws RemoteException {
+        }
+
+        @Override
+        public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
+                String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
+                throws RemoteException {
+        }
+
+        @Override
+        public void unstableProviderDied(IBinder iBinder) throws RemoteException {
+        }
+
+        @Override
+        public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1,
+                int i2) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b)
+                throws RemoteException {
+        }
+
+        @Override
+        public void setProcessState(int i) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException {
+        }
+
+        @Override
+        public void updateTimePrefs(int i) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException {
+        }
+
+        @Override
+        public void notifyCleartextNetwork(byte[] bytes) throws RemoteException {
+        }
+
+        @Override
+        public void startBinderTracking() throws RemoteException {
+        }
+
+        @Override
+        public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor)
+                throws RemoteException {
+        }
+
+        @Override
+        public void scheduleLocalVoiceInteractionStarted(IBinder iBinder,
+                IVoiceInteractor iVoiceInteractor) throws RemoteException {
+        }
+
+        @Override
+        public void handleTrustStorageUpdate() throws RemoteException {
+        }
+
+        @Override
+        public void attachAgent(String s) throws RemoteException {
+        }
+
+        @Override
+        public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo)
+                throws RemoteException {
+        }
+
+        @Override
+        public void setNetworkBlockSeq(long l) throws RemoteException {
+        }
+
+        @Override
+        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
+                ParcelFileDescriptor fd) {
+        }
+
+        @Override
+        public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 492ef72..4ce6029 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -127,6 +127,7 @@
                     Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
                     Settings.Global.BOOT_COUNT,
                     Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
                     Settings.Global.CAPTIVE_PORTAL_HTTPS_URL,
@@ -230,6 +231,7 @@
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
                     Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
                     Settings.Global.LOCK_SOUND,
                     Settings.Global.LOW_BATTERY_SOUND,
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
new file mode 100644
index 0000000..6dd787d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.DisplayCutout.NO_CUTOUT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DisplayCutout.ParcelableWrapper;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DisplayCutoutTest {
+
+    /** This is not a consistent cutout. Useful for verifying insets in one go though. */
+    final DisplayCutout mCutoutNumbers = new DisplayCutout(
+            new Rect(1, 2, 3, 4),
+            new Rect(5, 6, 7, 8),
+            Arrays.asList(
+                    new Point(9, 10),
+                    new Point(11, 12),
+                    new Point(13, 14),
+                    new Point(15, 16)));
+
+    final DisplayCutout mCutoutTop = createCutoutTop();
+
+    @Test
+    public void hasCutout() throws Exception {
+        assertFalse(NO_CUTOUT.hasCutout());
+        assertTrue(mCutoutTop.hasCutout());
+    }
+
+    @Test
+    public void getSafeInsets() throws Exception {
+        assertEquals(1, mCutoutNumbers.getSafeInsetLeft());
+        assertEquals(2, mCutoutNumbers.getSafeInsetTop());
+        assertEquals(3, mCutoutNumbers.getSafeInsetRight());
+        assertEquals(4, mCutoutNumbers.getSafeInsetBottom());
+
+        Rect safeInsets = new Rect();
+        mCutoutNumbers.getSafeInsets(safeInsets);
+
+        assertEquals(new Rect(1, 2, 3, 4), safeInsets);
+    }
+
+    @Test
+    public void getBoundingRect() throws Exception {
+        Rect boundingRect = new Rect();
+        mCutoutTop.getBoundingRect(boundingRect);
+
+        assertEquals(new Rect(50, 0, 75, 100), boundingRect);
+    }
+
+    @Test
+    public void getBoundingPolygon() throws Exception {
+        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        mCutoutTop.getBoundingPolygon(boundingPolygon);
+
+        assertEquals(Arrays.asList(
+                new Point(75, 0),
+                new Point(50, 0),
+                new Point(75, 100),
+                new Point(50, 100)), boundingPolygon);
+    }
+
+    @Test
+    public void testHashCode() throws Exception {
+        assertEquals(mCutoutTop.hashCode(), createCutoutTop().hashCode());
+        assertNotEquals(mCutoutTop.hashCode(), mCutoutNumbers.hashCode());
+    }
+
+    @Test
+    public void testEquals() throws Exception {
+        assertEquals(mCutoutTop, createCutoutTop());
+        assertNotEquals(mCutoutTop, mCutoutNumbers);
+    }
+
+    @Test
+    public void testToString() throws Exception {
+        assertFalse(mCutoutTop.toString().isEmpty());
+        assertFalse(mCutoutNumbers.toString().isEmpty());
+    }
+
+    @Test
+    public void inset_immutable() throws Exception {
+        DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
+
+        assertEquals("original instance must not be mutated", createCutoutTop(), mCutoutTop);
+    }
+
+    @Test
+    public void inset_insets_withLeftCutout() throws Exception {
+        DisplayCutout cutout = createCutoutWithInsets(100, 0, 0, 0).inset(1, 2, 3, 4);
+
+        assertEquals(cutout.getSafeInsetLeft(), 99);
+        assertEquals(cutout.getSafeInsetTop(), 0);
+        assertEquals(cutout.getSafeInsetRight(), 0);
+        assertEquals(cutout.getSafeInsetBottom(), 0);
+    }
+
+    @Test
+    public void inset_insets_withTopCutout() throws Exception {
+        DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
+
+        assertEquals(cutout.getSafeInsetLeft(), 0);
+        assertEquals(cutout.getSafeInsetTop(), 98);
+        assertEquals(cutout.getSafeInsetRight(), 0);
+        assertEquals(cutout.getSafeInsetBottom(), 0);
+    }
+
+    @Test
+    public void inset_insets_withRightCutout() throws Exception {
+        DisplayCutout cutout = createCutoutWithInsets(0, 0, 100, 0).inset(1, 2, 3, 4);
+
+        assertEquals(cutout.getSafeInsetLeft(), 0);
+        assertEquals(cutout.getSafeInsetTop(), 0);
+        assertEquals(cutout.getSafeInsetRight(), 97);
+        assertEquals(cutout.getSafeInsetBottom(), 0);
+    }
+
+    @Test
+    public void inset_insets_withBottomCutout() throws Exception {
+        DisplayCutout cutout = createCutoutWithInsets(0, 0, 0, 100).inset(1, 2, 3, 4);
+
+        assertEquals(cutout.getSafeInsetLeft(), 0);
+        assertEquals(cutout.getSafeInsetTop(), 0);
+        assertEquals(cutout.getSafeInsetRight(), 0);
+        assertEquals(cutout.getSafeInsetBottom(), 96);
+    }
+
+    @Test
+    public void inset_insets_consumeInset() throws Exception {
+        DisplayCutout cutout = mCutoutTop.inset(0, 1000, 0, 0);
+
+        assertEquals(cutout.getSafeInsetLeft(), 0);
+        assertEquals(cutout.getSafeInsetTop(), 0);
+        assertEquals(cutout.getSafeInsetRight(), 0);
+        assertEquals(cutout.getSafeInsetBottom(), 0);
+
+        assertFalse(cutout.hasCutout());
+    }
+
+    @Test
+    public void inset_bounds() throws Exception {
+        DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
+
+        Rect boundingRect = new Rect();
+        cutout.getBoundingRect(boundingRect);
+
+        assertEquals(new Rect(49, -2, 74, 98), boundingRect);
+
+        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        cutout.getBoundingPolygon(boundingPolygon);
+
+        assertEquals(Arrays.asList(
+                new Point(74, -2),
+                new Point(49, -2),
+                new Point(74, 98),
+                new Point(49, 98)), boundingPolygon);
+    }
+
+    @Test
+    public void calculateRelativeTo_top() throws Exception {
+        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400));
+
+        Rect insets = new Rect();
+        cutout.getSafeInsets(insets);
+
+        assertEquals(new Rect(0, 100, 0, 0), insets);
+    }
+
+    @Test
+    public void calculateRelativeTo_left() throws Exception {
+        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200));
+
+        Rect insets = new Rect();
+        cutout.getSafeInsets(insets);
+
+        assertEquals(new Rect(75, 0, 0, 0), insets);
+    }
+
+    @Test
+    public void calculateRelativeTo_bottom() throws Exception {
+        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100));
+
+        Rect insets = new Rect();
+        cutout.getSafeInsets(insets);
+
+        assertEquals(new Rect(0, 0, 0, 100), insets);
+    }
+
+    @Test
+    public void calculateRelativeTo_right() throws Exception {
+        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100));
+
+        Rect insets = new Rect();
+        cutout.getSafeInsets(insets);
+
+        assertEquals(new Rect(0, 0, 50, 0), insets);
+    }
+
+    @Test
+    public void calculateRelativeTo_bounds() throws Exception {
+        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200));
+
+
+        Rect boundingRect = new Rect();
+        cutout.getBoundingRect(boundingRect);
+        assertEquals(new Rect(1050, 2000, 1075, 2100), boundingRect);
+
+        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        cutout.getBoundingPolygon(boundingPolygon);
+
+        assertEquals(Arrays.asList(
+                new Point(1075, 2000),
+                new Point(1050, 2000),
+                new Point(1075, 2100),
+                new Point(1050, 2100)), boundingPolygon);
+    }
+
+    @Test
+    public void fromBoundingPolygon() throws Exception {
+        assertEquals(
+                new DisplayCutout(
+                        new Rect(0, 0, 0, 0), // fromBoundingPolygon won't calculate safe insets.
+                        new Rect(50, 0, 75, 100),
+                        Arrays.asList(
+                                new Point(75, 0),
+                                new Point(50, 0),
+                                new Point(75, 100),
+                                new Point(50, 100))),
+                DisplayCutout.fromBoundingPolygon(
+                        Arrays.asList(
+                                new Point(75, 0),
+                                new Point(50, 0),
+                                new Point(75, 100),
+                                new Point(50, 100))));
+    }
+
+    @Test
+    public void parcel_unparcel_regular() {
+        Parcel p = Parcel.obtain();
+
+        new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0);
+        int posAfterWrite = p.dataPosition();
+
+        p.setDataPosition(0);
+
+        assertEquals(mCutoutTop, ParcelableWrapper.CREATOR.createFromParcel(p).get());
+        assertEquals(posAfterWrite, p.dataPosition());
+    }
+
+    @Test
+    public void parcel_unparcel_nocutout() {
+        Parcel p = Parcel.obtain();
+
+        new ParcelableWrapper(NO_CUTOUT).writeToParcel(p, 0);
+        int posAfterWrite = p.dataPosition();
+
+        p.setDataPosition(0);
+
+        assertEquals(NO_CUTOUT, ParcelableWrapper.CREATOR.createFromParcel(p).get());
+        assertEquals(posAfterWrite, p.dataPosition());
+    }
+
+    @Test
+    public void parcel_unparcel_inplace() {
+        Parcel p = Parcel.obtain();
+
+        new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0);
+        int posAfterWrite = p.dataPosition();
+
+        p.setDataPosition(0);
+
+        ParcelableWrapper wrapper = new ParcelableWrapper();
+        wrapper.readFromParcel(p);
+
+        assertEquals(mCutoutTop, wrapper.get());
+        assertEquals(posAfterWrite, p.dataPosition());
+    }
+
+    @Test
+    public void wrapper_hashcode() throws Exception {
+        assertEquals(new ParcelableWrapper(mCutoutTop).hashCode(),
+                new ParcelableWrapper(createCutoutTop()).hashCode());
+        assertNotEquals(new ParcelableWrapper(mCutoutTop).hashCode(),
+                new ParcelableWrapper(mCutoutNumbers).hashCode());
+    }
+
+    @Test
+    public void wrapper_equals() throws Exception {
+        assertEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(createCutoutTop()));
+        assertNotEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(mCutoutNumbers));
+    }
+
+    private static DisplayCutout createCutoutTop() {
+        return new DisplayCutout(
+                new Rect(0, 100, 0, 0),
+                new Rect(50, 0, 75, 100),
+                Arrays.asList(
+                        new Point(75, 0),
+                        new Point(50, 0),
+                        new Point(75, 100),
+                        new Point(50, 100)));
+    }
+
+    private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
+        return new DisplayCutout(
+                new Rect(left, top, right, bottom),
+                new Rect(50, 0, 75, 100),
+                Arrays.asList(
+                        new Point(75, 0),
+                        new Point(50, 0),
+                        new Point(75, 100),
+                        new Point(50, 100)));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
similarity index 95%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index c8dc9ff..2f32d13 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,26 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility;
+package android.view.accessibility;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
+
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.support.test.runner.AndroidJUnit4;
-import android.view.accessibility.AccessibilityCache;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.View;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -48,27 +45,27 @@
 
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityCacheTest {
-    private static int WINDOW_ID_1 = 0xBEEF;
-    private static int WINDOW_ID_2 = 0xFACE;
-    private static int SINGLE_VIEW_ID = 0xCAFE;
-    private static int OTHER_VIEW_ID = 0xCAB2;
-    private static int PARENT_VIEW_ID = 0xFED4;
-    private static int CHILD_VIEW_ID = 0xFEED;
-    private static int OTHER_CHILD_VIEW_ID = 0xACE2;
-    private static int MOCK_CONNECTION_ID = 1;
+    private static final int WINDOW_ID_1 = 0xBEEF;
+    private static final int WINDOW_ID_2 = 0xFACE;
+    private static final int SINGLE_VIEW_ID = 0xCAFE;
+    private static final int OTHER_VIEW_ID = 0xCAB2;
+    private static final int PARENT_VIEW_ID = 0xFED4;
+    private static final int CHILD_VIEW_ID = 0xFEED;
+    private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
+    private static final int MOCK_CONNECTION_ID = 1;
 
     AccessibilityCache mAccessibilityCache;
     AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
-    AtomicInteger numA11yNodeInfosInUse = new AtomicInteger(0);
-    AtomicInteger numA11yWinInfosInUse = new AtomicInteger(0);
+    AtomicInteger mNumA11yNodeInfosInUse = new AtomicInteger(0);
+    AtomicInteger mNumA11yWinInfosInUse = new AtomicInteger(0);
 
     @Before
     public void setUp() {
         mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
         when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
         mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
-        AccessibilityNodeInfo.setNumInstancesInUseCounter(numA11yNodeInfosInUse);
-        AccessibilityWindowInfo.setNumInstancesInUseCounter(numA11yWinInfosInUse);
+        AccessibilityNodeInfo.setNumInstancesInUseCounter(mNumA11yNodeInfosInUse);
+        AccessibilityWindowInfo.setNumInstancesInUseCounter(mNumA11yWinInfosInUse);
     }
 
     @After
@@ -76,7 +73,8 @@
         // Make sure we're recycling all of our window and node infos
         mAccessibilityCache.clear();
         AccessibilityInteractionClient.getInstance().clearCache();
-        assertEquals(0, numA11yWinInfosInUse.get());
+        assertEquals(0, mNumA11yWinInfosInUse.get());
+        assertEquals(0, mNumA11yNodeInfosInUse.get());
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
new file mode 100644
index 0000000..8a5fc2d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for AccessibilityInteractionClient
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityInteractionClientTest {
+    private static final int MOCK_CONNECTION_ID = 0xabcd;
+
+    private MockConnection mMockConnection = new MockConnection();
+    @Mock private AccessibilityCache mMockCache;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+        AccessibilityInteractionClient.setCache(mMockCache);
+        AccessibilityInteractionClient.addConnection(MOCK_CONNECTION_ID, mMockConnection);
+    }
+
+    /**
+     * When the AccessibilityCache refreshes the nodes it contains, it gets very confused if
+     * it is called to update itself during the refresh. It tries to update the node that it's
+     * in the process of refreshing, which leads to AccessibilityNodeInfos in inconsistent states.
+     */
+    @Test
+    public void findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache() {
+        final int windowId = 0x1234;
+        final long accessibilityNodeId = 0x4321L;
+        AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
+        nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
+        mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection);
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
+                MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
+        assertEquals("Node got lost along the way", nodeFromConnection, node);
+
+        verifyZeroInteractions(mMockCache);
+    }
+
+    private static class MockConnection extends AccessibilityServiceConnectionImpl {
+        List<AccessibilityNodeInfo> mInfosToReturn;
+
+        @Override
+        public boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+                long accessibilityNodeId, int interactionId,
+                IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+                Bundle arguments) {
+            try {
+                callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
similarity index 78%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 7f97973..79e6316 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -1,10 +1,25 @@
-package com.android.server.accessibility;
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
 
 import static org.junit.Assert.fail;
 
 import android.support.test.runner.AndroidJUnit4;
 import android.util.ArraySet;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.internal.util.CollectionUtils;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
new file mode 100644
index 0000000..d3bbee7
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Region;
+import android.os.Bundle;
+
+import java.util.List;
+
+/**
+ * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
+ * all of the methods
+ */
+public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceConnection.Stub {
+    public void setServiceInfo(AccessibilityServiceInfo info) {}
+
+    public boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+            long accessibilityNodeId, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+            Bundle arguments) {
+        return false;
+    }
+
+    public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,
+            long accessibilityNodeId, String text, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long threadId) {
+        return false;
+    }
+
+    public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+            long accessibilityNodeId, String viewId, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long threadId) {
+        return false;
+    }
+
+    public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+            int interactionId, IAccessibilityInteractionConnectionCallback callback,
+            long threadId) {
+        return false;
+    }
+
+    public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+            int interactionId, IAccessibilityInteractionConnectionCallback callback,
+            long threadId) {
+        return false;
+    }
+
+    public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
+            int action, Bundle arguments, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long threadId) {
+        return false;
+    }
+
+    public AccessibilityWindowInfo getWindow(int windowId) {
+        return null;
+    }
+
+    public List<AccessibilityWindowInfo> getWindows() {
+        return null;
+    }
+
+    public AccessibilityServiceInfo getServiceInfo() {
+        return null;
+    }
+
+    public boolean performGlobalAction(int action) {
+        return false;
+    }
+
+    public void disableSelf() {}
+
+    public void setOnKeyEventResult(boolean handled, int sequence) {}
+
+    public float getMagnificationScale() {
+        return 0.0f;
+    }
+
+    public float getMagnificationCenterX() {
+        return 0.0f;
+    }
+
+    public float getMagnificationCenterY() {
+        return 0.0f;
+    }
+
+    public Region getMagnificationRegion() {
+        return null;
+    }
+
+    public boolean resetMagnification(boolean animate) {
+        return false;
+    }
+
+    public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+            boolean animate) {
+        return false;
+    }
+
+    public void setMagnificationCallbackEnabled(boolean enabled) {}
+
+    public boolean setSoftKeyboardShowMode(int showMode) {
+        return false;
+    }
+
+    public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
+
+    public boolean isAccessibilityButtonAvailable() {
+        return false;
+    }
+
+    public void sendGesture(int sequence, ParceledListSlice gestureSteps) {}
+
+    public boolean isFingerprintGestureDetectionAvailable() {
+        return false;
+    }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 41686fa..9092c85 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -45,6 +45,9 @@
 
     private TextClassificationManager mTcm;
     private TextClassifier mClassifier;
+    private TextSelection.Options mSelectionOptions;
+    private TextClassification.Options mClassificationOptions;
+    private TextLinks.Options mLinksOptions;
 
     @Before
     public void setup() {
@@ -52,6 +55,9 @@
                 .getSystemService(TextClassificationManager.class);
         mTcm.setTextClassifier(null);
         mClassifier = mTcm.getTextClassifier();
+        mSelectionOptions = new TextSelection.Options().setDefaultLocales(LOCALES);
+        mClassificationOptions = new TextClassification.Options().setDefaultLocales(LOCALES);
+        mLinksOptions = new TextLinks.Options().setDefaultLocales(LOCALES);
     }
 
     @Test
@@ -66,24 +72,9 @@
         int smartStartIndex = text.indexOf(suggested);
         int smartEndIndex = smartStartIndex + suggested.length();
 
-        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
-                isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
-    }
-
-    @Test
-    public void testSmartSelection_nullLocaleList() {
-        if (isTextClassifierDisabled()) return;
-
-        String text = "Contact me at droid@android.com";
-        String selected = "droid";
-        String suggested = "droid@android.com";
-        int startIndex = text.indexOf(selected);
-        int endIndex = startIndex + selected.length();
-        int smartStartIndex = text.indexOf(suggested);
-        int smartEndIndex = smartStartIndex + suggested.length();
-        LocaleList nullLocales = null;
-
-        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, nullLocales),
+        TextSelection selection = mClassifier.suggestSelection(
+                text, startIndex, endIndex, mSelectionOptions);
+        assertThat(selection,
                 isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
     }
 
@@ -99,7 +90,9 @@
         int smartStartIndex = text.indexOf(suggested);
         int smartEndIndex = smartStartIndex + suggested.length();
 
-        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
+        TextSelection selection = mClassifier.suggestSelection(
+                text, startIndex, endIndex, mSelectionOptions);
+        assertThat(selection,
                 isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
     }
 
@@ -112,7 +105,9 @@
         int startIndex = text.indexOf(selected);
         int endIndex = startIndex + selected.length();
 
-        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
+        TextSelection selection = mClassifier.suggestSelection(
+                text, startIndex, endIndex, mSelectionOptions);
+        assertThat(selection,
                 isTextSelection(startIndex, endIndex, NO_TYPE));
     }
 
@@ -124,7 +119,10 @@
         String classifiedText = "droid@android.com";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
-        assertThat(mClassifier.classifyText(text, startIndex, endIndex, LOCALES),
+
+        TextClassification classification = mClassifier.classifyText(
+                text, startIndex, endIndex, mClassificationOptions);
+        assertThat(classification,
                 isTextClassification(
                         classifiedText,
                         TextClassifier.TYPE_EMAIL,
@@ -139,7 +137,10 @@
         String classifiedText = "www.android.com";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
-        assertThat(mClassifier.classifyText(text, startIndex, endIndex, LOCALES),
+
+        TextClassification classification = mClassifier.classifyText(
+                text, startIndex, endIndex, mClassificationOptions);
+        assertThat(classification,
                 isTextClassification(
                         classifiedText,
                         TextClassifier.TYPE_URL,
@@ -154,7 +155,10 @@
         String classifiedText = "HTTP://ANDROID.COM";
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
-        assertThat(mClassifier.classifyText(text, startIndex, endIndex, LOCALES),
+
+        TextClassification classification = mClassifier.classifyText(
+                text, startIndex, endIndex, mClassificationOptions);
+        assertThat(classification,
                 isTextClassification(
                         classifiedText,
                         TextClassifier.TYPE_URL,
@@ -162,22 +166,6 @@
     }
 
     @Test
-    public void testTextClassifyText_nullLocaleList() {
-        if (isTextClassifierDisabled()) return;
-
-        String text = "Contact me at droid@android.com";
-        String classifiedText = "droid@android.com";
-        int startIndex = text.indexOf(classifiedText);
-        int endIndex = startIndex + classifiedText.length();
-        LocaleList nullLocales = null;
-        assertThat(mClassifier.classifyText(text, startIndex, endIndex, nullLocales),
-                isTextClassification(
-                        classifiedText,
-                        TextClassifier.TYPE_EMAIL,
-                        "mailto:" + classifiedText));
-    }
-
-    @Test
     public void testGenerateLinks() {
         if (isTextClassifierDisabled()) return;
 
@@ -210,13 +198,14 @@
         int startIndex = text.indexOf(classifiedText);
         int endIndex = startIndex + classifiedText.length();
 
-        Collection<TextLinks.TextLink> links = mClassifier.generateLinks(text, null).getLinks();
+        Collection<TextLinks.TextLink> links = mClassifier.generateLinks(text, mLinksOptions)
+                .getLinks();
         for (TextLinks.TextLink link : links) {
             if (text.subSequence(link.getStart(), link.getEnd()).equals(classifiedText)) {
                 assertEquals(type, link.getEntity(0));
                 assertEquals(startIndex, link.getStart());
                 assertEquals(endIndex, link.getEnd());
-                assertTrue(link.getConfidenceScore(type) > .9);
+                assertTrue(link.getConfidenceScore(type) > 0);
                 return;
             }
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
new file mode 100644
index 0000000..4b197e4
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BstatsCpuTimesValidationTest {
+    private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+
+    private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
+    private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+    private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
+
+    private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+    private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
+    private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
+
+    private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+    private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
+
+    private static final int GENERAL_TIMEOUT_MS = 1000;
+    private static final int GENERAL_INTERVAL_MS = 100;
+
+    private static final int WORK_DURATION_MS = 2000;
+
+    private static Context sContext;
+    private static UiDevice sUiDevice;
+    private static int sTestPkgUid;
+    private static boolean sCpuFreqTimesAvailable;
+
+    @BeforeClass
+    public static void setupOnce() throws Exception {
+        sContext = InstrumentationRegistry.getContext();
+        sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+        sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
+        sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+    }
+
+    // Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
+    // kernel node is available.
+    private static boolean cpuFreqTimesAvailable() throws Exception {
+        final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+        return cpuTimes != null;
+    }
+
+    @Test
+    public void testCpuFreqTimes() throws Exception {
+        if (!sCpuFreqTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null", initialSnapshot);
+        doSomeWork();
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_screenOff() throws Exception {
+        if (!sCpuFreqTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null", initialSnapshot);
+        doSomeWork();
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
+        assertCpuTimesValid(cpuTimesMs);
+        long actualTotalCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualTotalCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualTotalCpuTimeMs);
+        long actualScreenOffCpuTimeMs = 0;
+        for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+            actualScreenOffCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect screen-off cpu time",
+                WORK_DURATION_MS, actualScreenOffCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_isolatedProcess() throws Exception {
+        if (!sCpuFreqTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null", initialSnapshot);
+        doSomeWorkInIsolatedProcess();
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    private void assertCpuTimesValid(long[] cpuTimes) {
+        assertNotNull(cpuTimes);
+        for (int i = 0; i < cpuTimes.length; ++i) {
+            if (cpuTimes[i] < 0) {
+                fail("Malformed cpu times data (-ve values): " + Arrays.toString(cpuTimes));
+            }
+        }
+        final int numFreqs = cpuTimes.length / 2;
+        for (int i = 0; i < numFreqs; ++i) {
+            if (cpuTimes[i] < cpuTimes[numFreqs + i]) {
+                fail("Malformed cpu times data (screen-off > total)" + Arrays.toString(cpuTimes));
+            }
+        }
+    }
+
+    private void assertApproximateValue(String errorPrefix, long expectedValue, long actualValue) {
+        assertValueRange(errorPrefix, actualValue, expectedValue * 0.5, expectedValue * 1.5);
+    }
+
+    private void assertValueRange(String errorPrefix,
+            long actualvalue, double minValue, double maxValue) {
+        final String errorMsg = String.format(errorPrefix + "; actual=%s; min=%s; max=%s",
+                actualvalue, minValue, maxValue);
+        assertTrue(errorMsg, actualvalue < maxValue);
+        assertTrue(errorMsg, actualvalue > minValue);
+    }
+
+    private void doSomeWork() throws Exception {
+        final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startActivity());
+        receiver.doSomeWork(WORK_DURATION_MS);
+        receiver.finishHost();
+    }
+
+    private void doSomeWorkInIsolatedProcess() throws Exception {
+        final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+        receiver.doSomeWork(WORK_DURATION_MS);
+        receiver.finishHost();
+    }
+
+    private IBinder startIsolatedService() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final IBinder[] binders = new IBinder[1];
+        final ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                binders[0] = service;
+                latch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+            }
+        };
+        final Intent launchIntent = new Intent()
+                .setComponent(new ComponentName(TEST_PKG, ISOLATED_TEST_SERVICE));
+        sContext.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE
+                | Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_NOT_FOREGROUND);
+        if (latch.await(START_ISOLATED_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (binders[0] == null) {
+                fail("Receiver binder should not be null");
+            }
+            return binders[0];
+        } else {
+            fail("Timed out waiting for the isolated test service to start");
+        }
+        return null;
+    }
+
+    private IBinder startActivity() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Intent launchIntent = new Intent()
+                .setComponent(new ComponentName(TEST_PKG, TEST_ACTIVITY));
+        final Bundle extras = new Bundle();
+        final IBinder[] binders = new IBinder[1];
+        extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+            @Override
+            public void onActivityLaunched(IBinder receiver) {
+                binders[0] = receiver;
+                latch.countDown();
+            }
+        });
+        launchIntent.putExtras(extras);
+        sContext.startActivity(launchIntent);
+        if (latch.await(START_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (binders[0] == null) {
+                fail("Receiver binder should not be null");
+            }
+            return binders[0];
+        } else {
+            fail("Timed out waiting for the test activity to start; testUid=" + sTestPkgUid);
+        }
+        return null;
+    }
+
+    private static long[] getAllCpuFreqTimes(int uid) throws Exception {
+        final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+        final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+        final Matcher matcher = pattern.matcher(checkinDump);
+        if (!matcher.find()) {
+            return null;
+        }
+        final String[] uidTimesStr = matcher.group(1).split(",");
+        final int freqCount = Integer.parseInt(uidTimesStr[0]);
+        if (uidTimesStr.length != (2 * freqCount + 1)) {
+            fail("Malformed data: " + Arrays.toString(uidTimesStr));
+        }
+        final long[] cpuTimes = new long[freqCount * 2];
+        for (int i = 0; i < cpuTimes.length; ++i) {
+            cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+        }
+        return cpuTimes;
+    }
+
+    private void resetBatteryStats() throws Exception {
+        executeCmd("dumpsys batterystats --reset");
+    }
+
+    private void batteryOnScreenOn() throws Exception {
+        batteryOn();
+        screenOn();
+    }
+
+    private void batteryOnScreenOff() throws Exception {
+        batteryOn();
+        screenoff();
+    }
+
+    private void batteryOffScreenOn() throws Exception {
+        batteryOff();
+        screenOn();
+    }
+
+    private void batteryOn() throws Exception {
+        executeCmd("dumpsys battery unplug");
+        assertBatteryState(false);
+    }
+
+    private void batteryOff() throws Exception {
+        executeCmd("dumpsys battery reset");
+        assertBatteryState(true);
+    }
+
+    private void screenOn() throws Exception {
+        executeCmd("input keyevent KEYCODE_WAKEUP");
+        executeCmd("wm dismiss-keyguard");
+        assertKeyguardUnLocked();
+        assertScreenInteractive(true);
+    }
+
+    private void screenoff() throws Exception {
+        executeCmd("input keyevent KEYCODE_SLEEP");
+        assertScreenInteractive(false);
+    }
+
+    private void forceStop() throws Exception {
+        executeCmd("cmd activity force-stop " + TEST_PKG);
+        assertUidState(PROCESS_STATE_NONEXISTENT);
+    }
+
+    private void assertUidState(int state) throws Exception {
+        final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+        final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
+        assertEquals(state, uidState);
+    }
+
+    private void assertKeyguardUnLocked() {
+        final KeyguardManager keyguardManager =
+                (KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
+        assertDelayedCondition("Keyguard should be unlocked",
+                () -> !keyguardManager.isKeyguardLocked());
+    }
+
+    private void assertScreenInteractive(boolean interactive) {
+        final PowerManager powerManager =
+                (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
+        assertDelayedCondition("Unexpected screen interactive state",
+                () -> interactive == powerManager.isInteractive());
+    }
+
+    private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+        final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
+        while (SystemClock.uptimeMillis() <= endTime) {
+            if (condition.isTrue()) {
+                return;
+            }
+            SystemClock.sleep(GENERAL_INTERVAL_MS);
+        }
+        if (!condition.isTrue()) {
+            fail(errorMsg);
+        }
+    }
+
+    private void assertBatteryState(boolean pluggedIn) throws Exception {
+        final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
+        while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
+            Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
+        }
+        if (isDevicePluggedIn() != pluggedIn) {
+            fail("Timed out waiting for the plugged-in state to change,"
+                    + " expected pluggedIn: " + pluggedIn);
+        }
+    }
+
+    private boolean isDevicePluggedIn() {
+        final Intent batteryIntent = sContext.registerReceiver(null,
+                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
+    }
+
+    private String executeCmd(String cmd) throws Exception {
+        final String result = sUiDevice.executeShellCommand(cmd).trim();
+        Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
+        return result;
+    }
+
+    private static String executeCmdSilent(String cmd) throws Exception {
+        return sUiDevice.executeShellCommand(cmd).trim();
+    }
+
+    private interface ExpectedCondition {
+        boolean isTrue();
+    }
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 3fe730f..2daf733 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -40,6 +40,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
+import java.security.KeyPair;
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.UnrecoverableKeyException;
@@ -418,6 +419,18 @@
     @Nullable @WorkerThread
     public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias)
             throws KeyChainException, InterruptedException {
+        KeyPair keyPair = getKeyPair(context, alias);
+        if (keyPair != null) {
+            return keyPair.getPrivate();
+        }
+
+        return null;
+    }
+
+    /** @hide */
+    @Nullable @WorkerThread
+    public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias)
+            throws KeyChainException, InterruptedException {
         if (alias == null) {
             throw new NullPointerException("alias == null");
         }
@@ -439,7 +452,7 @@
             return null;
         } else {
             try {
-                return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+                return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
                         KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
             } catch (RuntimeException | UnrecoverableKeyException e) {
                 throw new KeyChainException(e);
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
deleted file mode 100644
index 4c150c8..0000000
--- a/legacy-test/Android.mk
+++ /dev/null
@@ -1,169 +0,0 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-# Build the legacy-test library
-# =============================
-# This contains the junit.framework and android.test classes that were in
-# Android API level 25 excluding those from android.test.runner.
-# Also contains the com.android.internal.util.Predicate[s] classes.
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := legacy-test
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
-
-include $(BUILD_JAVA_LIBRARY)
-
-# Build the repackaged-legacy-test library
-# ========================================
-# This contains repackaged versions of the classes from legacy-test.
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := repackaged-legacy-test
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Generate the stub source files for legacy.test.stubs
-# ====================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := \
-    core-oj \
-    core-libart \
-    framework \
-
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src
-
-LEGACY_TEST_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/api.txt
-LEGACY_TEST_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/removed.txt
-
-LEGACY_TEST_API_FILE := $(LOCAL_PATH)/api/legacy-test-current.txt
-LEGACY_TEST_REMOVED_API_FILE := $(LOCAL_PATH)/api/legacy-test-removed.txt
-
-LOCAL_DROIDDOC_OPTIONS:= \
-    -stubpackages android.test:android.test.suitebuilder.annotation:com.android.internal.util:junit.framework \
-    -stubsourceonly \
-    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/src \
-    -nodocs \
-    -api $(LEGACY_TEST_OUTPUT_API_FILE) \
-    -removedApi $(LEGACY_TEST_OUTPUT_REMOVED_API_FILE) \
-
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_MODULE := legacy-test-api-stubs-gen
-
-include $(BUILD_DROIDDOC)
-
-# Remember the target that will trigger the code generation.
-legacy_test_api_gen_stamp := $(full_target)
-
-# Add some additional dependencies
-$(LEGACY_TEST_OUTPUT_API_FILE): $(full_target)
-$(LEGACY_TEST_OUTPUT_REMOVED_API_FILE): $(full_target)
-
-# Build the legacy.test.stubs library
-# ===================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := legacy.test.stubs
-
-LOCAL_SOURCE_FILES_ALL_GENERATED := true
-LOCAL_SDK_VERSION := current
-LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
-
-# Make sure to run droiddoc first to generate the stub source files.
-LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Archive a copy of the classes.jar in SDK build.
-$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):legacy.test.stubs.jar)
-
-# Check that the legacy.test.stubs library has not changed
-# ========================================================
-
-# Check that the API we're building hasn't changed from the not-yet-released
-# SDK version.
-$(eval $(call check-api, \
-    check-legacy-test-api-current, \
-    $(LEGACY_TEST_API_FILE), \
-    $(LEGACY_TEST_OUTPUT_API_FILE), \
-    $(LEGACY_TEST_REMOVED_API_FILE), \
-    $(LEGACY_TEST_OUTPUT_REMOVED_API_FILE), \
-    -error 2 -error 3 -error 4 -error 5 -error 6 \
-    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
-    -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
-    -error 25 -error 26 -error 27, \
-    cat $(LOCAL_PATH)/api/apicheck_msg_legacy_test.txt, \
-    check-legacy-test-api, \
-    $(call doc-timestamp-for,legacy-test-api-stubs-gen) \
-    ))
-
-.PHONY: check-legacy-test-api
-checkapi: check-legacy-test-api
-
-.PHONY: update-legacy-test-api
-update-api: update-legacy-test-api
-
-update-legacy-test-api: $(LEGACY_TEST_OUTPUT_API_FILE) | $(ACP)
-	@echo Copying current.txt
-	$(hide) $(ACP) $(LEGACY_TEST_OUTPUT_API_FILE) $(LEGACY_TEST_API_FILE)
-	@echo Copying removed.txt
-	$(hide) $(ACP) $(LEGACY_TEST_OUTPUT_REMOVED_API_FILE) $(LEGACY_TEST_REMOVED_API_FILE)
-
-# Build the legacy-android-test library
-# =====================================
-# This contains the android.test classes that were in Android API level 25,
-# including those from android.test.runner.
-# Also contains the com.android.internal.util.Predicate[s] classes.
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src/android) \
-    $(call all-java-files-under, ../test-runner/src/android) \
-    $(call all-java-files-under, ../test-mock/src/android) \
-    $(call all-java-files-under, src/com)
-LOCAL_MODULE := legacy-android-test
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-ifeq ($(HOST_OS),linux)
-# Build the legacy-performance-test-hostdex library
-# =================================================
-# This contains the android.test.PerformanceTestCase class only
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := src/android/test/PerformanceTestCase.java
-LOCAL_MODULE := legacy-performance-test-hostdex
-
-include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
-endif  # HOST_OS == linux
-
-legacy_test_api_gen_stamp :=
diff --git a/legacy-test/api/apicheck_msg_legacy_test.txt b/legacy-test/api/apicheck_msg_legacy_test.txt
deleted file mode 100644
index ad5f235..0000000
--- a/legacy-test/api/apicheck_msg_legacy_test.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-
-******************************
-You have tried to change the API from what has been previously approved.
-
-To make these errors go away, you have two choices:
-   1) You can add "@hide" javadoc comments to the methods, etc. listed in the
-      errors above.
-
-   2) You can update legacy-test-current.txt by executing the following command:
-         make update-legacy-test-api
-
-      To submit the revised legacy-test-current.txt to the main Android repository,
-      you will need approval.
-******************************
-
-
-
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 158df13..da0205d 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -47,12 +47,12 @@
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
-  return ApkAssets::LoadImpl(path, nullptr, nullptr, system, false /*load_as_shared_library*/);
+  return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
                                                                 bool system) {
-  return ApkAssets::LoadImpl(path, nullptr, nullptr, system, true /*load_as_shared_library*/);
+  return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, true /*load_as_shared_library*/);
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
@@ -70,8 +70,15 @@
     LOG(ERROR) << "failed to load IDMAP " << idmap_path;
     return {};
   }
-  return LoadImpl(loaded_idmap->OverlayApkPath(), std::move(idmap_asset), std::move(loaded_idmap),
-                  system, false /*load_as_shared_library*/);
+  return LoadImpl({} /*fd*/, loaded_idmap->OverlayApkPath(), std::move(idmap_asset),
+                  std::move(loaded_idmap), system, false /*load_as_shared_library*/);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
+                                                       const std::string& friendly_name,
+                                                       bool system, bool force_shared_lib) {
+  return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
+                  system, force_shared_lib);
 }
 
 std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
@@ -96,11 +103,19 @@
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
-    const std::string& path, std::unique_ptr<Asset> idmap_asset,
+    unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
     std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
   ATRACE_CALL();
+
   ::ZipArchiveHandle unmanaged_handle;
-  int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
+  int32_t result;
+  if (fd >= 0) {
+    result =
+        ::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/);
+  } else {
+    result = ::OpenArchive(path.c_str(), &unmanaged_handle);
+  }
+
   if (result != 0) {
     LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
     return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 83c82af..94a05b2 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -427,7 +427,6 @@
   ATRACE_CALL();
   constexpr const int kMaxIterations = 20;
 
-  *out_last_reference = 0u;
   for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
                               in_out_value->data != 0u && iteration < kMaxIterations;
        iteration++) {
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 3a307fc..69702e3 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "android-base/macros.h"
+#include "android-base/unique_fd.h"
 
 #include "androidfw/Asset.h"
 #include "androidfw/LoadedArsc.h"
@@ -51,6 +52,16 @@
   static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
                                                       bool system = false);
 
+  // Creates an ApkAssets from the given file descriptor, and takes ownership of the file
+  // descriptor. The `friendly_name` is some name that will be used to identify the source of
+  // this ApkAssets in log messages and other debug scenarios.
+  // If `system` is true, the package is marked as a system package, and allows some functions to
+  // filter out this package when computing what configurations/resources are available.
+  // If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
+  static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd,
+                                                     const std::string& friendly_name, bool system,
+                                                     bool force_shared_lib);
+
   std::unique_ptr<Asset> Open(const std::string& path,
                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
 
@@ -69,7 +80,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
 
-  static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path,
+  static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path,
                                                    std::unique_ptr<Asset> idmap_asset,
                                                    std::unique_ptr<const LoadedIdmap> loaded_idmap,
                                                    bool system, bool load_as_shared_library);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index a77c4b9..b033137 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -210,7 +210,7 @@
   // are OR'd together with `in_out_flags`.
   // `in_out_config` is populated with the configuration for which the resolved value was defined.
   // `out_last_reference` is populated with the last reference ID before resolving to an actual
-  // value.
+  // value. This is only initialized if the passed in `in_out_value` is a reference.
   // Returns the cookie of the APK the resolved resource was defined in, or kInvalidCookie if
   // it was not found.
   ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index d65d93f..ba5844b 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -24,6 +24,7 @@
 #include "TestHelpers.h"
 #include "data/basic/R.h"
 
+using ::android::base::unique_fd;
 using ::com::android::basic::R;
 
 namespace android {
@@ -44,6 +45,26 @@
   ASSERT_NE(nullptr, asset);
 }
 
+TEST(ApkAssetsTest, LoadApkFromFd) {
+  const std::string path = GetTestDataPath() + "/basic/basic.apk";
+  unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
+  ASSERT_GE(fd.get(), 0);
+
+  std::unique_ptr<const ApkAssets> loaded_apk =
+      ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/);
+  ASSERT_NE(nullptr, loaded_apk);
+
+  const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
+  ASSERT_NE(nullptr, loaded_package);
+  EXPECT_TRUE(loaded_package->IsVerified());
+
+  std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
+  ASSERT_NE(nullptr, asset);
+}
+
 TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
@@ -132,7 +153,7 @@
   ASSERT_NE(nullptr, asset);
 
   off64_t start, length;
-  base::unique_fd fd(asset->openFileDescriptor(&start, &length));
+  unique_fd fd(asset->openFileDescriptor(&start, &length));
   EXPECT_GE(fd.get(), 0);
 
   lseek64(fd.get(), start, SEEK_SET);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index ab1a22e..567adfe 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -319,7 +319,7 @@
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
   EXPECT_EQ(basic::R::integer::ref2, value.data);
 
-  uint32_t last_ref;
+  uint32_t last_ref = 0u;
   cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
   ASSERT_NE(kInvalidCookie, cookie);
   EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
@@ -342,7 +342,7 @@
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
   EXPECT_EQ(basic::R::array::integerArray1, value.data);
 
-  uint32_t last_ref;
+  uint32_t last_ref = 0u;
   cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
   ASSERT_NE(kInvalidCookie, cookie);
   EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
@@ -350,6 +350,25 @@
   EXPECT_EQ(basic::R::array::integerArray1, last_ref);
 }
 
+TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({basic_assets_.get()});
+
+  ResTable_config selected_config;
+  memset(&selected_config, 0, sizeof(selected_config));
+
+  uint32_t flags = 0u;
+
+  // Create some kind of Res_value that is NOT a reference.
+  Res_value value;
+  value.dataType = Res_value::TYPE_STRING;
+  value.data = 0;
+
+  uint32_t last_ref = basic::R::string::test1;
+  EXPECT_EQ(1, assetmanager.ResolveReference(1, &value, &selected_config, &flags, &last_ref));
+  EXPECT_EQ(basic::R::string::test1, last_ref);
+}
+
 static bool IsConfigurationPresent(const std::set<ResTable_config>& configurations,
                                    const ResTable_config& configuration) {
   return configurations.count(configuration) > 0;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 508869a..eb0d161 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -738,6 +738,7 @@
     // care of all alignment.
     SkPaint paintCopy(paint);
     paintCopy.setTextAlign(SkPaint::kLeft_Align);
+    SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
 
     SkRect bounds =
             SkRect::MakeLTRB(boundsLeft + x, boundsTop + y, boundsRight + x, boundsBottom + y);
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 46d4ae7..845acc0 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -110,7 +110,8 @@
     // We only respect the nothingToDraw check when we are composing a layer. This
     // ensures that we paint the layer even if it is not currently visible in the
     // event that the properties change and it becomes visible.
-    if (!renderNode->isRenderable() || (renderNode->nothingToDraw() && mComposeLayer)) {
+    if ((mProjectedDisplayList == nullptr && !renderNode->isRenderable()) ||
+            (renderNode->nothingToDraw() && mComposeLayer)) {
         return;
     }
 
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 58c9980..6bae80c 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -36,6 +36,7 @@
         SkPaint textPaint;
         textPaint.setTextSize(dp(20));
         textPaint.setAntiAlias(true);
+        textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
 
         SkPoint pts[2];
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index 75b231d..fee0659 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -42,8 +42,10 @@
 
         mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
         mBluePaint.setTextSize(padding);
+        mBluePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
         mGreenPaint.setTextSize(padding);
+        mGreenPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
         // interleave drawText and drawRect with saveLayer ops
         for (int i = 0; i < regions; i++, top += smallRectHeight) {
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index dff259f..7dd271f 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -350,6 +350,72 @@
     EXPECT_EQ(3, canvas.getIndex());
 }
 
+RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+    class ProjectionTestCanvas : public SkCanvas {
+    public:
+        ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            mDrawCounter++;
+        }
+
+        int getDrawCounter() { return mDrawCounter; }
+
+    private:
+        int mDrawCounter = 0;
+    };
+
+    auto receiverBackground = TestUtils::createSkiaNode(
+            0, 0, 100, 100,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+                properties.setProjectionReceiver(true);
+            },
+            "B"); // a receiver with an empty display list
+
+    auto projectingRipple = TestUtils::createSkiaNode(
+            0, 0, 100, 100,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+                properties.setProjectBackwards(true);
+                properties.setClipToBounds(false);
+                SkPaint paint;
+                canvas.drawRect(0, 0, 100, 100, paint);
+            },
+            "P");
+    auto child = TestUtils::createSkiaNode(
+            0, 0, 100, 100,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+                SkPaint paint;
+                canvas.drawRect(0, 0, 100, 100, paint);
+                canvas.drawRenderNode(projectingRipple.get());
+            },
+            "C");
+    auto parent = TestUtils::createSkiaNode(
+            0, 0, 100, 100,
+            [&receiverBackground, &child](RenderProperties& properties,
+                                          SkiaRecordingCanvas& canvas) {
+                canvas.drawRenderNode(receiverBackground.get());
+                canvas.drawRenderNode(child.get());
+            },
+            "A");
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(
+            CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    parent->prepareTree(info);
+
+    // parent(A)             -> (receiverBackground, child)
+    // child(C)              -> (rect[0, 0, 100, 100], projectingRipple)
+    // projectingRipple(P)   -> (rect[0, 0, 100, 100]) -> projects backwards
+    // receiverBackground(B) -> (empty) -> projection receiver
+
+    // create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+    ProjectionTestCanvas canvas(100, 100);
+    RenderNodeDrawable drawable(parent.get(), &canvas, true);
+    canvas.drawDrawable(&drawable);
+    EXPECT_EQ(2, canvas.getDrawCounter());
+}
+
 RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
     /* R is backward projected on B and C is a layer.
                 A
diff --git a/location/tests/Android.mk b/location/tests/Android.mk
new file mode 100644
index 0000000..57848f3
--- /dev/null
+++ b/location/tests/Android.mk
@@ -0,0 +1,3 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index c78c99f..1019580 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -640,7 +640,6 @@
      * The ImageReader continues to be usable after this call, but may need to reallocate buffers
      * when more buffers are needed for rendering.
      * </p>
-     * @hide
      */
     public void discardFreeBuffers() {
         synchronized (mCloseLock) {
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7678490..3c49b80 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -25,6 +25,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Surface;
 
@@ -34,6 +35,8 @@
 import java.io.RandomAccessFile;
 import java.lang.ref.WeakReference;
 
+import com.android.internal.annotations.GuardedBy;
+
 /**
  * Used to record audio and video. The recording control is based on a
  * simple state machine (see below).
@@ -76,7 +79,7 @@
  * <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p>
  * </div>
  */
-public class MediaRecorder
+public class MediaRecorder implements AudioRouting
 {
     static {
         System.loadLibrary("media_jni");
@@ -1243,6 +1246,7 @@
         private static final int MEDIA_RECORDER_TRACK_EVENT_INFO       = 101;
         private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END   = 1000;
 
+        private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED  = 10000;
 
         @Override
         public void handleMessage(Message msg) {
@@ -1265,6 +1269,16 @@
 
                 return;
 
+            case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED:
+                AudioManager.resetAudioPortGeneration();
+                synchronized (mRoutingChangeListeners) {
+                    for (NativeRoutingEventHandlerDelegate delegate
+                            : mRoutingChangeListeners.values()) {
+                        delegate.notifyClient();
+                    }
+                }
+                return;
+
             default:
                 Log.e(TAG, "Unknown message type " + msg.what);
                 return;
@@ -1272,6 +1286,155 @@
         }
     }
 
+    //--------------------------------------------------------------------------
+    // Explicit Routing
+    //--------------------
+    private AudioDeviceInfo mPreferredDevice = null;
+
+    /**
+     * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+     * the input from this MediaRecorder.
+     * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio source.
+     *  If deviceInfo is null, default routing is restored.
+     * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+     * does not correspond to a valid audio input device.
+     */
+    @Override
+    public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+        if (deviceInfo != null && !deviceInfo.isSource()) {
+            return false;
+        }
+        int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+        boolean status = native_setInputDevice(preferredDeviceId);
+        if (status == true) {
+            synchronized (this) {
+                mPreferredDevice = deviceInfo;
+            }
+        }
+        return status;
+    }
+
+    /**
+     * Returns the selected input device specified by {@link #setPreferredDevice}. Note that this
+     * is not guaranteed to correspond to the actual device being used for recording.
+     */
+    @Override
+    public AudioDeviceInfo getPreferredDevice() {
+        synchronized (this) {
+            return mPreferredDevice;
+        }
+    }
+
+    /**
+     * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder
+     * Note: The query is only valid if the MediaRecorder is currently recording.
+     * If the recorder is not recording, the returned device can be null or correspond to previously
+     * selected device when the recorder was last active.
+     */
+    @Override
+    public AudioDeviceInfo getRoutedDevice() {
+        int deviceId = native_getRoutedDeviceId();
+        if (deviceId == 0) {
+            return null;
+        }
+        AudioDeviceInfo[] devices =
+                AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
+        for (int i = 0; i < devices.length; i++) {
+            if (devices[i].getId() == deviceId) {
+                return devices[i];
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+     */
+    private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+        if (mRoutingChangeListeners.size() == 0) {
+            native_enableDeviceCallback(enabled);
+        }
+    }
+
+    /**
+     * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+     * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+     * by an app to receive (re)routing notifications.
+     */
+    @GuardedBy("mRoutingChangeListeners")
+    private ArrayMap<AudioRouting.OnRoutingChangedListener,
+            NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+    /**
+     * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+     * changes on this MediaRecorder.
+     * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+     * notifications of rerouting events.
+     * @param handler  Specifies the {@link Handler} object for the thread on which to execute
+     * the callback. If <code>null</code>, the handler on the main looper will be used.
+     */
+    @Override
+    public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+                                            Handler handler) {
+        synchronized (mRoutingChangeListeners) {
+            if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+                enableNativeRoutingCallbacksLocked(true);
+                mRoutingChangeListeners.put(
+                        listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+            }
+        }
+    }
+
+    /**
+     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+     * to receive rerouting notifications.
+     * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+     * to remove.
+     */
+    @Override
+    public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+        synchronized (mRoutingChangeListeners) {
+            if (mRoutingChangeListeners.containsKey(listener)) {
+                mRoutingChangeListeners.remove(listener);
+                enableNativeRoutingCallbacksLocked(false);
+            }
+        }
+    }
+
+    /**
+     * Helper class to handle the forwarding of native events to the appropriate listener
+     * (potentially) handled in a different thread
+     */
+    private class NativeRoutingEventHandlerDelegate {
+        private MediaRecorder mMediaRecorder;
+        private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+        private Handler mHandler;
+
+        NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder,
+                final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+            mMediaRecorder = mediaRecorder;
+            mOnRoutingChangedListener = listener;
+            mHandler = handler != null ? handler : mEventHandler;
+        }
+
+        void notifyClient() {
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mOnRoutingChangedListener != null) {
+                            mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder);
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    private native final boolean native_setInputDevice(int deviceId);
+    private native final int native_getRoutedDeviceId();
+    private native final void native_enableDeviceCallback(boolean enabled);
+
     /**
      * Called from native code when an interesting event happens.  This method
      * just uses the EventHandler system to post the event back to the main app thread.
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 6c87a9d..143182f 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1586,8 +1586,10 @@
      * @param info The TV input which will use the acquired Hardware.
      * @return Hardware on success, {@code null} otherwise.
      *
+     * @hide
      * @removed
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
     public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback,
             TvInputInfo info) {
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 497684c..d2bc174 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -657,6 +657,56 @@
     return mybundle;
 
 }
+
+static jboolean
+android_media_MediaRecorder_setInputDevice(JNIEnv *env, jobject thiz, jint device_id)
+{
+    ALOGV("android_media_MediaRecorder_setInputDevice");
+
+    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+    if (mr == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    if (process_media_recorder_call(env, mr->setInputDevice(device_id),
+            "java/lang/RuntimeException", "setInputDevice failed.")) {
+        return false;
+    }
+    return true;
+}
+
+static jint
+android_media_MediaRecorder_getRoutedDeviceId(JNIEnv *env, jobject thiz)
+{
+    ALOGV("android_media_MediaRecorder_getRoutedDeviceId");
+
+    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+    if (mr == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return AUDIO_PORT_HANDLE_NONE;
+    }
+
+    audio_port_handle_t deviceId;
+    process_media_recorder_call(env, mr->getRoutedDeviceId(&deviceId),
+            "java/lang/RuntimeException", "getRoutedDeviceId failed.");
+    return (jint) deviceId;
+}
+
+static void
+android_media_MediaRecorder_enableDeviceCallback(JNIEnv *env, jobject thiz, jboolean enabled)
+{
+    ALOGV("android_media_MediaRecorder_enableDeviceCallback %d", enabled);
+
+    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+    if (mr == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    process_media_recorder_call(env, mr->enableAudioDeviceCallback(enabled),
+            "java/lang/RuntimeException", "enableDeviceCallback failed.");
+}
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -689,6 +739,10 @@
     {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
 
     {"native_getMetrics",    "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics},
+
+    {"native_setInputDevice", "(I)Z",                           (void *)android_media_MediaRecorder_setInputDevice},
+    {"native_getRoutedDeviceId", "()I",                         (void *)android_media_MediaRecorder_getRoutedDeviceId},
+    {"native_enableDeviceCallback", "(Z)V",                      (void *)android_media_MediaRecorder_enableDeviceCallback},
 };
 
 // This function only registers the native methods, and is called from
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java
index 56a675a..15ea367 100644
--- a/obex/javax/obex/ServerOperation.java
+++ b/obex/javax/obex/ServerOperation.java
@@ -195,7 +195,12 @@
             if(!handleObexPacket(packet)) {
                 return;
             }
-            if (!mHasBody) {
+            /* Don't Pre-Send continue when Remote requested for SRM
+             * Let the Application confirm.
+             */
+            if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled
+                    + " not hasBody case: " + mHasBody);
+            if (!mHasBody && !mSrmEnabled) {
                 while ((!mGetOperation) && (!finalBitSet)) {
                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
                     if (mPrivateInput.available() > 0) {
@@ -204,8 +209,13 @@
                 }
             }
         }
-
-        while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
+        /* Don't Pre-Send continue when Remote requested for SRM
+          * Let the Application confirm.
+          */
+        if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled
+            + " not finalPacket: " + finalBitSet + " not GETOp Case: " + mGetOperation);
+        while ((!mSrmEnabled) && (!mGetOperation) && (!finalBitSet)
+                && (mPrivateInput.available() == 0)) {
             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
             if (mPrivateInput.available() > 0) {
                 break;
diff --git a/packages/CompanionDeviceManager/res/drawable/dialog_background.xml b/packages/CompanionDeviceManager/res/drawable/dialog_background.xml
index af2c83f..a017f41 100644
--- a/packages/CompanionDeviceManager/res/drawable/dialog_background.xml
+++ b/packages/CompanionDeviceManager/res/drawable/dialog_background.xml
@@ -16,7 +16,7 @@
 
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape android:shape="rectangle">
-        <corners android:radius="2dp" />
+        <corners android:radius="?android:attr/dialogCornerRadius" />
         <solid android:color="?android:attr/colorBackground" />
     </shape>
 </inset>
diff --git a/packages/SettingsLib/res/layout/preference_dropdown_material_settings.xml b/packages/SettingsLib/res/layout/preference_dropdown_material_settings.xml
deleted file mode 100644
index a0b8155..0000000
--- a/packages/SettingsLib/res/layout/preference_dropdown_material_settings.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2017 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-
-<!-- Based off frameworks/base/core/res/res/layout/preference_dropdown_material.xml
-     except that icon space in this layout is always reserved -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content">
-
-    <Spinner
-        android:id="@+id/spinner"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/preference_no_icon_padding_start"
-        android:visibility="invisible" />
-
-    <include layout="@layout/preference_material"/>
-
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/styles_support_preference.xml b/packages/SettingsLib/res/values/styles_support_preference.xml
index cf9f3c6..d7032b8 100644
--- a/packages/SettingsLib/res/values/styles_support_preference.xml
+++ b/packages/SettingsLib/res/values/styles_support_preference.xml
@@ -20,73 +20,14 @@
 
     <dimen name="preference_no_icon_padding_start">72dp</dimen>
 
-    <!-- Fragment style -->
-    <style name="PreferenceFragmentStyle.SettingsBase" parent="@*android:style/PreferenceFragment.Material">
-        <item name="allowDividerAfterLastItem">false</item>
-    </style>
-
-    <!-- Preferences -->
-    <style name="Preference.SettingsBase" parent="@style/Preference.Material">
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="singleLineTitle">false</item>
-        <item name="iconSpaceReserved">true</item>
-    </style>
-
-    <!-- Preference category -->
-    <style name="Preference.Category.SettingsBase" parent="@style/Preference.Category.Material">
-        <item name="allowDividerAbove">true</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="android:layout">@layout/preference_category_material_settings</item>
-    </style>
-
-    <!-- Preference screen -->
-    <style name="Preference.Screen.SettingsBase" parent="@style/Preference.PreferenceScreen.Material">
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
-    </style>
-
     <!-- Footer Preferences -->
-    <style name="Preference.FooterPreference.SettingsBase" parent="Preference.SettingsBase">
+    <style name="Preference.FooterPreference.SettingsBase" parent="@style/Preference.Material">
         <item name="android:layout">@layout/preference_footer</item>
         <item name="allowDividerAbove">true</item>
     </style>
 
-    <!-- Dropdown Preferences -->
-    <style name="Preference.DropdownPreference.SettingsBase" parent="Preference.SettingsBase">
-        <item name="android:layout">@layout/preference_dropdown_material_settings</item>
-    </style>
-
-    <!-- Switch Preferences -->
-    <style name="Preference.SwitchPreference.SettingsBase" parent="@style/Preference.SwitchPreference.Material">
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
-        <item name="singleLineTitle">false</item>
-    </style>
-
-    <!-- EditText Preferences -->
-    <style name="Preference.EditTextPreference.SettingsBase"
-           parent="@style/Preference.DialogPreference.EditTextPreference.Material">
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
-        <item name="singleLineTitle">false</item>
-    </style>
-
     <style name="PreferenceThemeOverlay.SettingsBase" parent="@style/PreferenceThemeOverlay.v14.Material">
-        <!-- Parent path frameworks/support/v14/preference/res/values/themes.xml -->
-        <item name="android:scrollbars">vertical</item>
-        <item name="preferenceFragmentStyle">@style/PreferenceFragmentStyle.SettingsBase</item>
-        <item name="preferenceCategoryStyle">@style/Preference.Category.SettingsBase</item>
-        <item name="preferenceScreenStyle">@style/Preference.Screen.SettingsBase</item>
-        <item name="preferenceStyle">@style/Preference.SettingsBase</item>
-        <item name="dialogPreferenceStyle">@style/Preference.SettingsBase</item>
-        <item name="editTextPreferenceStyle">@style/Preference.EditTextPreference.SettingsBase</item>
         <item name="footerPreferenceStyle">@style/Preference.FooterPreference.SettingsBase</item>
-        <item name="switchPreferenceStyle">@style/Preference.SwitchPreference.SettingsBase</item>
-        <item name="dropdownPreferenceStyle">@style/Preference.DropdownPreference.SettingsBase</item>
     </style>
 
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml
index 12d31cf..6a8d780 100644
--- a/packages/SettingsLib/res/xml/timezones.xml
+++ b/packages/SettingsLib/res/xml/timezones.xml
@@ -27,6 +27,7 @@
     <timezone id="Atlantic/South_Georgia"></timezone>
     <timezone id="Atlantic/Azores"></timezone>
     <timezone id="Atlantic/Cape_Verde"></timezone>
+    <timezone id="Etc/UTC"></timezone>
     <timezone id="Africa/Casablanca"></timezone>
     <timezone id="Europe/London"></timezone>
     <timezone id="Europe/Amsterdam"></timezone>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 2186169..eb33842 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -105,7 +105,8 @@
             }
         }
         return new UserIconDrawable(iconSize).setIconDrawable(
-                UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake();
+                UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+                .bake();
     }
 
     /** Formats a double from 0.0..100.0 with an option to round **/
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
index ee7885d..0703330 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
@@ -18,7 +18,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -27,7 +26,6 @@
 import com.android.settingslib.applications.InterestingConfigChanges;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -104,10 +102,10 @@
         }
         for (int i = 0; i < mCategories.size(); i++) {
             DashboardCategory category = mCategories.get(i);
-            for (int j = 0; j < category.tiles.size(); j++) {
-                Tile tile = category.tiles.get(j);
+            for (int j = 0; j < category.getTilesCount(); j++) {
+                Tile tile = category.getTile(j);
                 if (tileBlacklist.contains(tile.intent.getComponent())) {
-                    category.tiles.remove(j--);
+                    category.removeTile(j--);
                 }
             }
         }
@@ -181,7 +179,7 @@
                         newCategory = new DashboardCategory();
                         categoryByKeyMap.put(newCategoryKey, newCategory);
                     }
-                    newCategory.tiles.add(tile);
+                    newCategory.addTile(tile);
                 }
             }
         }
@@ -198,7 +196,7 @@
     synchronized void sortCategories(Context context,
             Map<String, DashboardCategory> categoryByKeyMap) {
         for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
-            sortCategoriesForExternalTiles(context, categoryEntry.getValue());
+            categoryEntry.getValue().sortTiles(context.getPackageName());
         }
     }
 
@@ -210,16 +208,16 @@
     synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
         for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
             final DashboardCategory category = categoryEntry.getValue();
-            final int count = category.tiles.size();
+            final int count = category.getTilesCount();
             final Set<ComponentName> components = new ArraySet<>();
             for (int i = count - 1; i >= 0; i--) {
-                final Tile tile = category.tiles.get(i);
+                final Tile tile = category.getTile(i);
                 if (tile.intent == null) {
                     continue;
                 }
                 final ComponentName tileComponent = tile.intent.getComponent();
                 if (components.contains(tileComponent)) {
-                    category.tiles.remove(i);
+                    category.removeTile(i);
                 } else {
                     components.add(tileComponent);
                 }
@@ -234,28 +232,7 @@
      */
     private synchronized void sortCategoriesForExternalTiles(Context context,
             DashboardCategory dashboardCategory) {
-        final String skipPackageName = context.getPackageName();
+        dashboardCategory.sortTiles(context.getPackageName());
 
-        // Sort tiles based on [priority, package within priority]
-        Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> {
-            final String package1 = tile1.intent.getComponent().getPackageName();
-            final String package2 = tile2.intent.getComponent().getPackageName();
-            final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
-            // First sort by priority
-            final int priorityCompare = tile2.priority - tile1.priority;
-            if (priorityCompare != 0) {
-                return priorityCompare;
-            }
-            // Then sort by package name, skip package take precedence
-            if (packageCompare != 0) {
-                if (TextUtils.equals(package1, skipPackageName)) {
-                    return -1;
-                }
-                if (TextUtils.equals(package2, skipPackageName)) {
-                    return 1;
-                }
-            }
-            return packageCompare;
-        });
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index f6f8168..a966e82 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.drawer;
 
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -23,6 +25,8 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 public class DashboardCategory implements Parcelable {
@@ -48,39 +52,59 @@
     /**
      * List of the category's children
      */
-    public List<Tile> tiles = new ArrayList<>();
+    private List<Tile> mTiles = new ArrayList<>();
 
+    DashboardCategory(DashboardCategory in) {
+        if (in != null) {
+            title = in.title;
+            key = in.key;
+            priority = in.priority;
+            for (Tile tile : in.mTiles) {
+                mTiles.add(tile);
+            }
+        }
+    }
 
     public DashboardCategory() {
         // Empty
     }
 
+    /**
+     * Get a copy of the list of the category's children.
+     *
+     * Note: the returned list serves as a read-only list. If tiles needs to be added or removed
+     * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}.
+     */
+    public List<Tile> getTiles() {
+        return Collections.unmodifiableList(mTiles);
+    }
+
     public void addTile(Tile tile) {
-        tiles.add(tile);
+        mTiles.add(tile);
     }
 
     public void addTile(int n, Tile tile) {
-        tiles.add(n, tile);
+        mTiles.add(n, tile);
     }
 
     public void removeTile(Tile tile) {
-        tiles.remove(tile);
+        mTiles.remove(tile);
     }
 
     public void removeTile(int n) {
-        tiles.remove(n);
+        mTiles.remove(n);
     }
 
     public int getTilesCount() {
-        return tiles.size();
+        return mTiles.size();
     }
 
     public Tile getTile(int n) {
-        return tiles.get(n);
+        return mTiles.get(n);
     }
 
     public boolean containsComponent(ComponentName component) {
-        for (Tile tile : tiles) {
+        for (Tile tile : mTiles) {
             if (TextUtils.equals(tile.intent.getComponent().getClassName(),
                     component.getClassName())) {
                 if (DEBUG) {
@@ -95,6 +119,40 @@
         return false;
     }
 
+    /**
+     * Sort priority value for tiles in this category.
+     */
+    public void sortTiles() {
+        Collections.sort(mTiles, TILE_COMPARATOR);
+    }
+
+    /**
+     * Sort priority value and package name for tiles in this category.
+     */
+    public void sortTiles(String skipPackageName) {
+        // Sort mTiles based on [priority, package within priority]
+        Collections.sort(mTiles, (tile1, tile2) -> {
+            final String package1 = tile1.intent.getComponent().getPackageName();
+            final String package2 = tile2.intent.getComponent().getPackageName();
+            final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
+            // First sort by priority
+            final int priorityCompare = tile2.priority - tile1.priority;
+            if (priorityCompare != 0) {
+                return priorityCompare;
+            }
+            // Then sort by package name, skip package take precedence
+            if (packageCompare != 0) {
+                if (TextUtils.equals(package1, skipPackageName)) {
+                    return -1;
+                }
+                if (TextUtils.equals(package2, skipPackageName)) {
+                    return 1;
+                }
+            }
+            return packageCompare;
+        });
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -106,11 +164,11 @@
         dest.writeString(key);
         dest.writeInt(priority);
 
-        final int count = tiles.size();
+        final int count = mTiles.size();
         dest.writeInt(count);
 
         for (int n = 0; n < count; n++) {
-            Tile tile = tiles.get(n);
+            Tile tile = mTiles.get(n);
             tile.writeToParcel(dest, flags);
         }
     }
@@ -124,7 +182,7 @@
 
         for (int n = 0; n < count; n++) {
             Tile tile = Tile.CREATOR.createFromParcel(in);
-            tiles.add(tile);
+            mTiles.add(tile);
         }
     }
 
@@ -141,4 +199,13 @@
             return new DashboardCategory[size];
         }
     };
+
+    public static final Comparator<Tile> TILE_COMPARATOR =
+            new Comparator<Tile>() {
+                @Override
+                public int compare(Tile lhs, Tile rhs) {
+                    return rhs.priority - lhs.priority;
+                }
+            };
+
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 038dcf8..e986e0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -253,7 +253,7 @@
         }
         ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
         for (DashboardCategory category : categories) {
-            Collections.sort(category.tiles, TILE_COMPARATOR);
+            category.sortTiles();
         }
         Collections.sort(categories, CATEGORY_COMPARATOR);
         if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
@@ -595,14 +595,6 @@
         return pathSegments.get(0);
     }
 
-    public static final Comparator<Tile> TILE_COMPARATOR =
-            new Comparator<Tile>() {
-        @Override
-        public int compare(Tile lhs, Tile rhs) {
-            return rhs.priority - lhs.priority;
-        }
-    };
-
     private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
             new Comparator<DashboardCategory>() {
         @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
index b27d823..8a09df2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
@@ -64,7 +64,8 @@
                 if (um.getUserIcon(userId) != null) {
                     icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
                 } else {
-                    icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+                    icon = UserIcons.getDefaultUserIcon(
+                            context.getResources(), userId, /* light= */ false);
                 }
             }
             this.mIcon = encircle(context, icon);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
index d7eae5f..f099c90 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
@@ -95,8 +95,9 @@
         mCategoryManager.backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
 
         assertThat(mCategoryByKeyMap.size()).isEqualTo(2);
-        assertThat(mCategoryByKeyMap.get(CategoryKey.CATEGORY_ACCOUNT).tiles.size()).isEqualTo(1);
-        assertThat(mCategoryByKeyMap.get(oldCategory).tiles.size()).isEqualTo(1);
+        assertThat(
+                mCategoryByKeyMap.get(CategoryKey.CATEGORY_ACCOUNT).getTilesCount()).isEqualTo(1);
+        assertThat(mCategoryByKeyMap.get(oldCategory).getTilesCount()).isEqualTo(1);
     }
 
     @Test
@@ -114,9 +115,10 @@
         // Added 1 more category to category map.
         assertThat(mCategoryByKeyMap.size()).isEqualTo(2);
         // The new category map has CATEGORY_NETWORK type now, which contains 1 tile.
-        assertThat(mCategoryByKeyMap.get(CategoryKey.CATEGORY_NETWORK).tiles.size()).isEqualTo(1);
+        assertThat(
+                mCategoryByKeyMap.get(CategoryKey.CATEGORY_NETWORK).getTilesCount()).isEqualTo(1);
         // Old category still exists.
-        assertThat(mCategoryByKeyMap.get(oldCategory).tiles.size()).isEqualTo(1);
+        assertThat(mCategoryByKeyMap.get(oldCategory).getTilesCount()).isEqualTo(1);
     }
 
     @Test
@@ -136,9 +138,9 @@
         tile3.intent =
                 new Intent().setComponent(new ComponentName(testPackage, "class3"));
         tile3.priority = 200;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         // Sort their priorities
@@ -146,9 +148,9 @@
                 mCategoryByKeyMap);
 
         // Verify they are now sorted.
-        assertThat(category.tiles.get(0)).isSameAs(tile3);
-        assertThat(category.tiles.get(1)).isSameAs(tile1);
-        assertThat(category.tiles.get(2)).isSameAs(tile2);
+        assertThat(category.getTile(0)).isSameAs(tile3);
+        assertThat(category.getTile(1)).isSameAs(tile1);
+        assertThat(category.getTile(2)).isSameAs(tile2);
     }
 
     @Test
@@ -169,9 +171,9 @@
         tile3.intent =
                 new Intent().setComponent(new ComponentName(testPackage1, "class3"));
         tile3.priority = 50;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         // Sort their priorities
@@ -179,9 +181,9 @@
                 mCategoryByKeyMap);
 
         // Verify they are now sorted.
-        assertThat(category.tiles.get(0)).isSameAs(tile2);
-        assertThat(category.tiles.get(1)).isSameAs(tile1);
-        assertThat(category.tiles.get(2)).isSameAs(tile3);
+        assertThat(category.getTile(0)).isSameAs(tile2);
+        assertThat(category.getTile(1)).isSameAs(tile1);
+        assertThat(category.getTile(2)).isSameAs(tile3);
     }
 
     @Test
@@ -202,9 +204,9 @@
         tile3.intent =
                 new Intent().setComponent(new ComponentName(testPackage, "class3"));
         tile3.priority = 50;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         // Sort their priorities
@@ -212,9 +214,9 @@
                 mCategoryByKeyMap);
 
         // Verify the sorting order is not changed
-        assertThat(category.tiles.get(0)).isSameAs(tile1);
-        assertThat(category.tiles.get(1)).isSameAs(tile2);
-        assertThat(category.tiles.get(2)).isSameAs(tile3);
+        assertThat(category.getTile(0)).isSameAs(tile1);
+        assertThat(category.getTile(1)).isSameAs(tile2);
+        assertThat(category.getTile(2)).isSameAs(tile3);
     }
 
     @Test
@@ -236,10 +238,10 @@
         final Tile tile4 = new Tile();
         tile4.intent = new Intent().setComponent(new ComponentName(testPackage, "class3"));
         tile4.priority = -1;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
-        category.tiles.add(tile4);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        category.addTile(tile4);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         // Sort their priorities
@@ -247,10 +249,10 @@
             mCategoryByKeyMap);
 
         // Verify the sorting order is not changed
-        assertThat(category.tiles.get(0)).isSameAs(tile1);
-        assertThat(category.tiles.get(1)).isSameAs(tile2);
-        assertThat(category.tiles.get(2)).isSameAs(tile3);
-        assertThat(category.tiles.get(3)).isSameAs(tile4);
+        assertThat(category.getTile(0)).isSameAs(tile1);
+        assertThat(category.getTile(1)).isSameAs(tile2);
+        assertThat(category.getTile(2)).isSameAs(tile3);
+        assertThat(category.getTile(3)).isSameAs(tile4);
     }
 
     @Test
@@ -270,9 +272,9 @@
         final Tile tile3 = new Tile();
         tile3.intent = new Intent().setComponent(new ComponentName(testPackage3, "class3"));
         tile3.priority = 1;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         // Sort their priorities
@@ -280,9 +282,9 @@
             mCategoryByKeyMap);
 
         // Verify the sorting order is internal first, follow by package name ordering
-        assertThat(category.tiles.get(0)).isSameAs(tile2);
-        assertThat(category.tiles.get(1)).isSameAs(tile3);
-        assertThat(category.tiles.get(2)).isSameAs(tile1);
+        assertThat(category.getTile(0)).isSameAs(tile2);
+        assertThat(category.getTile(1)).isSameAs(tile3);
+        assertThat(category.getTile(2)).isSameAs(tile1);
     }
 
     @Test
@@ -303,14 +305,14 @@
         tile3.intent =
                 new Intent().setComponent(new ComponentName(testPackage, "class3"));
         tile3.priority = 50;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
 
-        assertThat(category.tiles.size()).isEqualTo(3);
+        assertThat(category.getTilesCount()).isEqualTo(3);
     }
 
     @Test
@@ -331,13 +333,13 @@
         tile3.intent =
                 new Intent().setComponent(new ComponentName(testPackage, "class1"));
         tile3.priority = 50;
-        category.tiles.add(tile1);
-        category.tiles.add(tile2);
-        category.tiles.add(tile3);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
         mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
 
-        assertThat(category.tiles.size()).isEqualTo(1);
+        assertThat(category.getTilesCount()).isEqualTo(1);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index dad3a28..a395a4a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -216,7 +216,7 @@
         List<DashboardCategory> categoryList = TileUtils.getCategories(
                 mContext, cache, false /* categoryDefinedInManifest */, testAction,
                 TileUtils.SETTING_PKG);
-        assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory);
+        assertThat(categoryList.get(0).getTile(0).category).isEqualTo(testCategory);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index ba1c9e3..52b4f4d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -801,6 +801,9 @@
                 Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
                 GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
         dumpSetting(s, p,
+                Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
+                GlobalSettingsProto.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX);
+        dumpSetting(s, p,
                 Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
                 GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 258c96c..7fb6ede 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -61,6 +61,7 @@
 import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -343,7 +344,8 @@
             }
 
             case Settings.CALL_METHOD_GET_SECURE: {
-                Setting setting = getSecureSetting(name, requestingUserId);
+                Setting setting = getSecureSetting(name, requestingUserId,
+                        /*enableOverride=*/ true);
                 return packageValueForCallResult(setting, isTrackingGeneration(args));
             }
 
@@ -1073,6 +1075,10 @@
     }
 
     private Setting getSecureSetting(String name, int requestingUserId) {
+        return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false);
+    }
+
+    private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
         }
@@ -1102,6 +1108,14 @@
                 return getSsaidSettingLocked(callingPkg, owningUserId);
             }
         }
+        if (enableOverride) {
+            if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+                final Setting overridden = getLocationProvidersAllowedSetting(owningUserId);
+                if (overridden != null) {
+                    return overridden;
+                }
+            }
+        }
 
         // Not the SSAID; do a straight lookup
         synchronized (mLock) {
@@ -1190,6 +1204,35 @@
         return null;
     }
 
+    private Setting getLocationProvidersAllowedSetting(int owningUserId) {
+        synchronized (mLock) {
+            final Setting setting = getGlobalSetting(
+                    Global.LOCATION_GLOBAL_KILL_SWITCH);
+            if (!"1".equals(setting.getValue())) {
+                return null;
+            }
+            // Global kill-switch is enabled. Return an empty value.
+            final SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+                    SETTINGS_TYPE_SECURE, owningUserId);
+            return settingsState.new Setting(
+                    Secure.LOCATION_PROVIDERS_ALLOWED,
+                    "", // value
+                    "", // tag
+                    "", // default value
+                    "", // package name
+                    false, // from system
+                    "0" // id
+            ) {
+                @Override
+                public boolean update(String value, boolean setDefault, String packageName,
+                        String tag, boolean forceNonSystemPackage) {
+                    Slog.wtf(LOG_TAG, "update shoudln't be called on this instance.");
+                    return false;
+                }
+            };
+        }
+    }
+
     private boolean insertSecureSetting(String name, String value, String tag,
             boolean makeDefault, int requestingUserId, boolean forceNotify) {
         if (DEBUG) {
@@ -2780,6 +2823,12 @@
             }
 
             mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+
+            // When the global kill switch is updated, send the change notification for
+            // the location setting.
+            if (isGlobalSettingsKey(key) && Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) {
+                notifyLocationChangeForRunningUsers();
+            }
         }
 
         private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
@@ -2799,6 +2848,24 @@
             }
         }
 
+        private void notifyLocationChangeForRunningUsers() {
+            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+
+            for (int i = 0; i < users.size(); i++) {
+                final int userId = users.get(i).id;
+
+                if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
+                    continue;
+                }
+
+                final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+                final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED);
+
+                mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
+                        userId, 0, uri).sendToTarget();
+            }
+        }
+
         private boolean isGlobalSettingsKey(int key) {
             return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
         }
@@ -2885,7 +2952,7 @@
                         } catch (SecurityException e) {
                             Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e);
                         }
-                        if (DEBUG) {
+                        if (DEBUG || true) {
                             Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
                         }
                     } break;
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
new file mode 100644
index 0000000..3c5f01b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_account_circle.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M24,0C10.8,0 0,10.8 0,24s10.8,24 24,24s24,-10.8 24,-24S37.200001,0 24,0zM24,7.2c3.96,0 7.2,3.24 7.2,7.2s-3.24,7.2 -7.2,7.2s-7.2,-3.24 -7.2,-7.2S20.040001,7.2 24,7.2zM24,41.279999c-6,0 -11.28,-3.12 -14.4,-7.68c0.12,-4.8 9.6,-7.44 14.4,-7.44s14.28,2.64 14.4,7.44C35.279999,38.16 30,41.279999 24,41.279999z"
+        android:fillColor="?attr/wallpaperTextColor"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 6df1657..2e35d86 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -126,6 +126,7 @@
         android:layout_height="48dp"
         android:orientation="horizontal"
         android:gravity="end"
+        android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
         android:layout_marginBottom="8dp" >
         <TextView
             android:id="@+id/more_settings"
@@ -139,7 +140,6 @@
             android:text="@string/notification_done"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_marginEnd="8dp"
             style="@style/TextAppearance.NotificationInfo.Button"/>
     </LinearLayout>
 </com.android.systemui.statusbar.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 3209f27..7476abd 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -35,7 +35,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
-            android:paddingStart="16dp"
+            android:paddingStart="@*android:dimen/notification_content_margin_start"
             android:textColor="#DD000000"
             android:paddingEnd="4dp"/>
 
@@ -53,7 +53,7 @@
             style="@style/TextAppearance.NotificationInfo.Button"
             android:layout_width="wrap_content"
             android:layout_height="36dp"
-            android:layout_marginEnd="8dp"
+            android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
             android:layout_alignParentEnd="true"
             android:layout_centerVertical="true"
             android:text="@string/snooze_undo" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index cf79238..8e065d1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -250,10 +250,7 @@
     <string name="doze_brightness_sensor_type" translatable="false"></string>
 
     <!-- Doze: pulse parameter - how long does it take to fade in? -->
-    <integer name="doze_pulse_duration_in">900</integer>
-
-    <!-- Doze: pulse parameter - how long does it take to fade in after a pickup? -->
-    <integer name="doze_pulse_duration_in_pickup">130</integer>
+    <integer name="doze_pulse_duration_in">130</integer>
 
     <!-- Doze: pulse parameter - once faded in, how long does it stay visible? -->
     <integer name="doze_pulse_duration_visible">6000</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f0bad2a..0715d49 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -72,16 +72,19 @@
     <dimen name="status_bar_connected_device_bt_indicator_size">17dp</dimen>
 
     <!-- Height of a small notification in the status bar-->
-    <dimen name="notification_min_height">92dp</dimen>
+    <dimen name="notification_min_height">100dp</dimen>
 
     <!-- Increased height of a small notification in the status bar -->
-    <dimen name="notification_min_height_increased">132dp</dimen>
+    <dimen name="notification_min_height_increased">140dp</dimen>
 
     <!-- Height of a small notification in the status bar which was used before android N -->
     <dimen name="notification_min_height_legacy">64dp</dimen>
 
+    <!-- Height of a small notification in the status bar which was used before android P -->
+    <dimen name="notification_min_height_before_p">92dp</dimen>
+
     <!-- Height of a large notification in the status bar -->
-    <dimen name="notification_max_height">284dp</dimen>
+    <dimen name="notification_max_height">292dp</dimen>
 
     <!-- Height of an ambient notification on ambient display -->
     <dimen name="notification_ambient_height">400dp</dimen>
@@ -89,12 +92,21 @@
     <!-- Height of a heads up notification in the status bar for legacy custom views -->
     <dimen name="notification_max_heads_up_height_legacy">128dp</dimen>
 
+    <!-- Height of a heads up notification in the status bar for custom views before andoid P -->
+    <dimen name="notification_max_heads_up_height_before_p">148dp</dimen>
+
     <!-- Height of a heads up notification in the status bar -->
-    <dimen name="notification_max_heads_up_height">148dp</dimen>
+    <dimen name="notification_max_heads_up_height">156dp</dimen>
 
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
 
+    <!-- Side padding on the lockscreen on the side of notifications -->
+    <dimen name="notification_lockscreen_side_paddings">8dp</dimen>
+
+    <!-- Additional side padding for custom content if the app doesn't target P yet -->
+    <dimen name="notification_content_custom_view_side_padding">@dimen/notification_lockscreen_side_paddings</dimen>
+
     <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
          and the notification won't use this much, but is measured with wrap_content -->
     <dimen name="notification_messaging_actions_min_height">196dp</dimen>
@@ -109,7 +121,7 @@
     <dimen name="notification_min_interaction_height">40dp</dimen>
 
     <!-- the padding of the shelf icon container -->
-    <dimen name="shelf_icon_container_padding">13dp</dimen>
+    <dimen name="shelf_icon_container_padding">21dp</dimen>
 
     <!-- The padding of a notification icon on top to the start of the notification. Used for custom
          views where the distance can't be measured -->
@@ -213,6 +225,9 @@
          etc. -->
     <dimen name="qs_footer_height">48dp</dimen>
 
+    <!-- The padding between the notifications and the quick settings container -->
+    <dimen name="qs_notification_keyguard_padding">8dp</dimen>
+
     <!-- Height of the status bar header bar when expanded -->
     <dimen name="status_bar_header_height_expanded">124dp</dimen>
 
@@ -348,10 +363,6 @@
     <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
     <dimen name="glowpadview_inner_radius">15dip</dimen>
 
-    <!-- bottom_stack_peek_amount + notification_min_height
-         + notification_collapse_second_card_padding -->
-    <dimen name="min_stack_height">104dp</dimen>
-
     <!-- Z distance between notifications if they are in the stack -->
     <dimen name="z_distance_between_notifications">0.5dp</dimen>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cfd95b4..dd31365 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -47,6 +47,7 @@
     <item type="id" name="qs_icon_tag"/>
     <item type="id" name="qs_slash_tag"/>
     <item type="id" name="scrim"/>
+    <item type="id" name="scrim_blanking"/>
     <item type="id" name="scrim_target"/>
     <item type="id" name="scrim_alpha_start"/>
     <item type="id" name="scrim_alpha_end"/>
@@ -77,6 +78,18 @@
     <item type="id" name="action_move_tl_30" />
     <item type="id" name="action_move_rb_full" />
 
+    <item type="id" name="bottom_roundess_animator_tag"/>
+    <item type="id" name="bottom_roundess_animator_start_tag"/>
+    <item type="id" name="bottom_roundess_animator_end_tag"/>
+
+    <item type="id" name="top_roundess_animator_tag"/>
+    <item type="id" name="top_roundess_animator_start_tag"/>
+    <item type="id" name="top_roundess_animator_end_tag"/>
+
+    <item type="id" name="side_padding_animator_tag"/>
+    <item type="id" name="side_padding_animator_start_tag"/>
+    <item type="id" name="side_padding_animator_end_tag"/>
+
     <!-- Accessibility actions for the notification menu -->
     <item type="id" name="action_snooze_undo"/>
     <item type="id" name="action_snooze_shorter"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 12f75bb..0219db3 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -307,8 +307,9 @@
 
     void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
                                                    int removedCount, int addedCount) {
-        if (AccessibilityManager.getInstance(mContext).isEnabled() &&
-                (isFocused() || isSelected() && isShown())) {
+        if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+                    AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
+                && (isFocused() || isSelected() && isShown())) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
             event.setFromIndex(fromIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 0c067ff..526a8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@
 
     public ScrimController createScrimController(LightBarController lightBarController,
             ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-            LockscreenWallpaper lockscreenWallpaper,
-            Consumer<Boolean> scrimVisibleListener) {
+            LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+            DozeParameters dozeParameters) {
         return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                scrimVisibleListener);
+                scrimVisibleListener, dozeParameters);
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 7db118d..2f607ee 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@
     boolean isBlockingDoze();
 
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
-    void abortPulsing();
     void extendPulse();
 
     void setAnimateWakeup(boolean animateWakeup);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b99e76a..c92acd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2028,12 +2028,9 @@
     }
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
-            ViewGroup container,
-            ScrimController scrimController,
-            FingerprintUnlockController fingerprintUnlockController) {
+            ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
-                scrimController, fingerprintUnlockController,
-                mDismissCallbackRegistry);
+                fingerprintUnlockController, mDismissCallbackRegistry);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0f..51175d1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -387,7 +387,9 @@
             }
             case MotionEvent.ACTION_HOVER_ENTER:
             case MotionEvent.ACTION_HOVER_MOVE: {
-                if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
+                if (mAccessibilityManager.isObservedEventType(
+                                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
+                        && !mSendingHoverAccessibilityEvents) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                     event.setImportantForAccessibility(true);
@@ -400,7 +402,9 @@
                 break;
             }
             case MotionEvent.ACTION_HOVER_EXIT: {
-                if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
+                if (mAccessibilityManager.isObservedEventType(
+                                AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
+                        && mSendingHoverAccessibilityEvents) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
                     event.setImportantForAccessibility(true);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index eef43d2..a984680 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -625,9 +625,7 @@
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             if (getState() != STATE_NO_PIP) {
                 boolean hasPip = false;
 
@@ -662,9 +660,7 @@
         @Override
         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             StackInfo stackInfo = getPinnedStackInfo();
             if (stackInfo == null) {
                 Log.w(TAG, "Cannot find pinned stack");
@@ -690,9 +686,7 @@
         @Override
         public void onPinnedActivityRestartAttempt(boolean clearedTask) {
             if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
             movePipToFullscreen();
         }
@@ -700,9 +694,7 @@
         @Override
         public void onPinnedStackAnimationEnded() {
             if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             switch (getState()) {
                 case STATE_PIP_MENU:
                     showPipMenu();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 3b1b2f9..663f206 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -23,14 +23,12 @@
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
@@ -156,7 +153,8 @@
                     // Launched from app is always the worst case (in terms of how many
                     // thumbnails/tasks visible)
                     launchState.launchedFromApp = true;
-                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
+                            -1 /* lastScrollPPresent */);
                     VisibilityReport visibilityReport =
                             mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
                                     stack.getTasks());
@@ -656,13 +654,6 @@
         // the resize mode already.
         if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
-            showRecents(
-                    false /* triggeredFromAltTab */,
-                    dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
-                    false /* animate */,
-                    true /* launchedWhileDockingTask*/,
-                    false /* fromHome */,
-                    DividerView.INVALID_RECENTS_GROW_TARGET);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index cf3cae5..2d3080b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -68,7 +68,6 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImpl;
@@ -372,7 +371,7 @@
         if (mIam == null) return false;
 
         try {
-            return mIam.isInLockTaskMode();
+            return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
         } catch (RemoteException e) {
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 600da04..d9f79bb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -431,7 +431,7 @@
      * in the stack.
      */
     public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
-            RecentsActivityLaunchState launchState) {
+            RecentsActivityLaunchState launchState, float lastScrollPPercent) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Clear the progress map
@@ -506,6 +506,8 @@
 
             if (launchState.launchedWithAltTab) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+            } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
+                mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
             } else if (Recents.getConfiguration().isLowRamDevice) {
                 mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
                         scrollToFront);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 1197501..36c9095 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -209,6 +209,9 @@
     private int mLastHeight;
     private boolean mStackActionButtonVisible;
 
+    // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
+    private float mLastScrollPPercent;
+
     // We keep track of the task view focused by user interaction and draw a frame around it in the
     // grid layout.
     private TaskViewFocusFrame mTaskViewFocusFrame;
@@ -327,6 +330,7 @@
             mStackScroller.reset();
             mStableLayoutAlgorithm.reset();
             mLayoutAlgorithm.reset();
+            mLastScrollPPercent = -1;
         }
 
         // Since we always animate to the same place in (the initial state), always reset the stack
@@ -822,7 +826,7 @@
    public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
            RecentsActivityLaunchState launchState) {
         // Compute the min and max scroll values
-        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
+        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
 
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
@@ -1150,6 +1154,8 @@
         if (mTaskViewsClipDirty) {
             clipTaskViews();
         }
+        mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
+            mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 84b7015..ff0357a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -695,6 +695,11 @@
                 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
     }
 
+    protected void updateBackgroundClipping() {
+        mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+        mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+    }
+
     protected boolean shouldHideBackground() {
         return mDark;
     }
@@ -901,12 +906,45 @@
         contentView.setAlpha(contentAlpha);
     }
 
+    @Override
+    protected void applyRoundness() {
+        super.applyRoundness();
+        applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+                getCurrentBackgroundRadiusBottom());
+    }
+
+    protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+        mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+        mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+    }
+
+    @Override
+    protected void setBackgroundTop(int backgroundTop) {
+        mBackgroundDimmed.setBackgroundTop(backgroundTop);
+        mBackgroundNormal.setBackgroundTop(backgroundTop);
+    }
+
     protected abstract View getContentView();
 
     public int calculateBgColor() {
         return calculateBgColor(true /* withTint */, true /* withOverRide */);
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        super.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+    }
+
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+            return true;
+        }
+        return super.childNeedsClipping(child);
+    }
+
     /**
      * @param withTint should a possible tint be factored in?
      * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6349275..8e1b104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -82,6 +82,7 @@
     private static final int MSG_TOGGLE_PANEL                  = 35 << MSG_SHIFT;
     private static final int MSG_SHOW_SHUTDOWN_UI              = 36 << MSG_SHIFT;
     private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR  = 37 << MSG_SHIFT;
+    private static final int MSG_ROTATION_PROPOSAL             = 38 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -142,6 +143,8 @@
         default void handleSystemKey(int arg1) { }
         default void handleShowGlobalActionsMenu() { }
         default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+        default void onRotationProposal(int rotation) { }
     }
 
     @VisibleForTesting
@@ -458,6 +461,15 @@
         }
     }
 
+    @Override
+    public void onProposedRotationChanged(int rotation) {
+        synchronized (mLock) {
+            mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+            mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0,
+                    null).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -654,6 +666,11 @@
                         mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
                     }
                     break;
+                case MSG_ROTATION_PROPOSAL:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).onRotationProposal(msg.arg1);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ff950e..23d9cae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -102,7 +102,9 @@
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mNotificationMinHeightLegacy;
+    private int mNotificationMinHeightBeforeP;
     private int mMaxHeadsUpHeightLegacy;
+    private int mMaxHeadsUpHeightBeforeP;
     private int mMaxHeadsUpHeight;
     private int mMaxHeadsUpHeightIncreased;
     private int mNotificationMinHeight;
@@ -435,9 +437,10 @@
         boolean customView = layout.getContractedChild().getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+        boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
         int minHeight;
-        if (customView && beforeN && !mIsSummaryWithChildren) {
-            minHeight = mNotificationMinHeightLegacy;
+        if (customView && beforeP && !mIsSummaryWithChildren) {
+            minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
             minHeight = mNotificationMinHeightLarge;
         } else {
@@ -447,8 +450,8 @@
                 layout.getHeadsUpChild().getId()
                         != com.android.internal.R.id.status_bar_latest_event_content;
         int headsUpheight;
-        if (headsUpCustom && beforeN) {
-            headsUpheight = mMaxHeadsUpHeightLegacy;
+        if (headsUpCustom && beforeP) {
+            headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
         } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
             headsUpheight = mMaxHeadsUpHeightIncreased;
         } else {
@@ -535,6 +538,7 @@
         }
         onChildrenCountChanged();
         row.setIsChildInGroup(false, null);
+        row.setBottomRoundness(0.0f, false /* animate */);
     }
 
     @Override
@@ -563,6 +567,7 @@
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateIconVisibilities();
+        updateBackgroundClipping();
     }
 
     @Override
@@ -916,6 +921,7 @@
             addView(mMenuRow.getMenuView(), menuIndex);
         }
         for (NotificationContentView l : mLayouts) {
+            l.initView();
             l.reInflateViews();
         }
         mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1025,6 +1031,7 @@
         mKeepInParent = keepInParent;
     }
 
+    @Override
     public boolean isRemoved() {
         return mRemoved;
     }
@@ -1264,6 +1271,8 @@
     private void initDimens() {
         mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_legacy);
+        mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height_before_p);
         mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height);
         mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
@@ -1274,6 +1283,8 @@
                 R.dimen.notification_ambient_height);
         mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
+        mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_heads_up_height_before_p);
         mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height);
         mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
@@ -1752,6 +1763,7 @@
         mPrivateLayout.updateExpandButtons(isExpandable());
         updateChildrenHeaderAppearance();
         updateChildrenVisibility();
+        applyChildrenRoundness();
     }
 
     public void updateChildrenHeaderAppearance() {
@@ -2332,6 +2344,56 @@
         }
     }
 
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationContentView) {
+            NotificationContentView contentView = (NotificationContentView) child;
+            if (isClippingNeeded()) {
+                return true;
+            } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+                return true;
+            }
+        } else if (child == mChildrenContainer) {
+            if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging())
+                    && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) {
+                return true;
+            }
+        } else if (child instanceof NotificationGuts) {
+            return !hasNoRoundingAndNoPadding();
+        }
+        return super.childNeedsClipping(child);
+    }
+
+    @Override
+    protected void applyRoundness() {
+        super.applyRoundness();
+        applyChildrenRoundness();
+    }
+
+    private void applyChildrenRoundness() {
+        if (mIsSummaryWithChildren) {
+            mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+        }
+    }
+
+    @Override
+    public Path getCustomClipPath(View child) {
+        if (child instanceof NotificationGuts) {
+            return getClipPath(true, /* ignoreTranslation */
+                    false /* clipRoundedToBottom */);
+        }
+        if (child instanceof NotificationChildrenContainer) {
+            return getClipPath(false, /* ignoreTranslation */
+                    true /* clipRoundedToBottom */);
+        }
+        return super.getCustomClipPath(child);
+    }
+
+    private boolean hasNoRoundingAndNoPadding() {
+        return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+                && getCurrentTopRoundness() == 0.0f;
+    }
+
     public boolean isShowingAmbient() {
         return mShowAmbient;
     }
@@ -2344,6 +2406,20 @@
         }
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        if (mIsSummaryWithChildren) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mChildrenContainer.getNotificationChildren();
+            int size = notificationChildren.size();
+            for (int i = 0; i < size; i++) {
+                ExpandableNotificationRow row = notificationChildren.get(i);
+                row.setCurrentSidePaddings(currentSidePaddings);
+            }
+        }
+        super.setCurrentSidePaddings(currentSidePaddings);
+    }
+
     public static class NotificationViewState extends ExpandableViewState {
 
         private final StackScrollState mOverallState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 2556890..b3d6e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,23 +18,58 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Outline;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
  * Like {@link ExpandableView}, but setting an outline for the height and clipping.
  */
 public abstract class ExpandableOutlineView extends ExpandableView {
 
+    private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+            "topRoundness",
+            ExpandableOutlineView::setTopRoundnessInternal,
+            ExpandableOutlineView::getCurrentTopRoundness,
+            R.id.top_roundess_animator_tag,
+            R.id.top_roundess_animator_end_tag,
+            R.id.top_roundess_animator_start_tag);
+    private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+            "bottomRoundness",
+            ExpandableOutlineView::setBottomRoundnessInternal,
+            ExpandableOutlineView::getCurrentBottomRoundness,
+            R.id.bottom_roundess_animator_tag,
+            R.id.bottom_roundess_animator_end_tag,
+            R.id.bottom_roundess_animator_start_tag);
+    private static final AnimationProperties ROUNDNESS_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    private static final Path EMPTY_PATH = new Path();
+
     private final Rect mOutlineRect = new Rect();
     private boolean mCustomOutline;
     private float mOutlineAlpha = -1f;
     private float mOutlineRadius;
+    private boolean mAlwaysRoundBothCorners;
+    private Path mTmpPath = new Path();
+    private Path mTmpPath2 = new Path();
+    private float mCurrentBottomRoundness;
+    private float mCurrentTopRoundness;
+    private float mBottomRoundness;
+    private float mTopRoundness;
+    private int mBackgroundTop;
+    protected int mCurrentSidePaddings;
 
     /**
      * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +80,248 @@
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
         public void getOutline(View view, Outline outline) {
-            int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
-            if (!mCustomOutline) {
-                outline.setRoundRect(translation,
-                        mClipTopAmount,
-                        getWidth() + translation,
-                        Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
-                        mOutlineRadius);
-            } else {
-                outline.setRoundRect(mOutlineRect, mOutlineRadius);
+            Path clipPath = getClipPath();
+            if (clipPath != null && clipPath.isConvex()) {
+                // The path might not be convex in border cases where the view is small and clipped
+                outline.setConvexPath(clipPath);
             }
             outline.setAlpha(mOutlineAlpha);
         }
     };
 
+    private Path getClipPath() {
+        return getClipPath(false, /* ignoreTranslation */
+                false /* clipRoundedToBottom */);
+    }
+
+    protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+        int left;
+        int top;
+        int right;
+        int bottom;
+        int height;
+        Path intersectPath = null;
+        if (!mCustomOutline) {
+            int translation = mShouldTranslateContents && !ignoreTranslation
+                    ? (int) getTranslation() : 0;
+            left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+            top = mClipTopAmount + mBackgroundTop;
+            right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+            bottom = Math.max(getActualHeight(), top);
+            int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+            if (bottom != intersectBottom) {
+                if (clipRoundedToBottom) {
+                    bottom = intersectBottom;
+                } else {
+                    getRoundedRectPath(left, top, right,
+                            intersectBottom, 0.0f,
+                            0.0f, mTmpPath2);
+                    intersectPath = mTmpPath2;
+                }
+            }
+        } else {
+            left = mOutlineRect.left;
+            top = mOutlineRect.top;
+            right = mOutlineRect.right;
+            bottom = mOutlineRect.bottom;
+            left = Math.max(mCurrentSidePaddings, left);
+            right = Math.min(getWidth() - mCurrentSidePaddings, right);
+        }
+        height = bottom - top;
+        if (height == 0) {
+            return EMPTY_PATH;
+        }
+        float topRoundness = mAlwaysRoundBothCorners
+                ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+        float bottomRoundness = mAlwaysRoundBothCorners
+                ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+        if (topRoundness + bottomRoundness > height) {
+            float overShoot = topRoundness + bottomRoundness - height;
+            topRoundness -= overShoot * mCurrentTopRoundness
+                    / (mCurrentTopRoundness + mCurrentBottomRoundness);
+            bottomRoundness -= overShoot * mCurrentBottomRoundness
+                    / (mCurrentTopRoundness + mCurrentBottomRoundness);
+        }
+        getRoundedRectPath(left, top, right, bottom, topRoundness,
+                bottomRoundness, mTmpPath);
+        Path roundedRectPath = mTmpPath;
+        if (intersectPath != null) {
+            roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+        }
+        return roundedRectPath;
+    }
+
+    protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+            float bottomRoundness) {
+        getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
+                mTmpPath);
+        return mTmpPath;
+    }
+
+    private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+            float bottomRoundness, Path outPath) {
+        outPath.reset();
+        int width = right - left;
+        float topRoundnessX = topRoundness;
+        float bottomRoundnessX = bottomRoundness;
+        topRoundnessX = Math.min(width / 2, topRoundnessX);
+        bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+        if (topRoundness > 0.0f) {
+            outPath.moveTo(left, top + topRoundness);
+            outPath.quadTo(left, top, left + topRoundnessX, top);
+            outPath.lineTo(right - topRoundnessX, top);
+            outPath.quadTo(right, top, right, top + topRoundness);
+        } else {
+            outPath.moveTo(left, top);
+            outPath.lineTo(right, top);
+        }
+        if (bottomRoundness > 0.0f) {
+            outPath.lineTo(right, bottom - bottomRoundness);
+            outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+            outPath.lineTo(left + bottomRoundnessX, bottom);
+            outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+        } else {
+            outPath.lineTo(right, bottom);
+            outPath.lineTo(left, bottom);
+        }
+        outPath.close();
+    }
+
     public ExpandableOutlineView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOutlineProvider(mProvider);
         initDimens();
     }
 
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        canvas.save();
+        if (childNeedsClipping(child)) {
+            Path clipPath = getCustomClipPath(child);
+            if (clipPath == null) {
+                clipPath = getClipPath();
+            }
+            if (clipPath != null) {
+                canvas.clipPath(clipPath);
+            }
+        }
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        canvas.restore();
+        return result;
+    }
+
+    protected boolean childNeedsClipping(View child) {
+        return false;
+    }
+
+    protected boolean isClippingNeeded() {
+        return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+    }
+
     private void initDimens() {
         Resources res = getResources();
         mShouldTranslateContents =
                 res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
         mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
-        setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+        mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+        if (!mAlwaysRoundBothCorners) {
+            mOutlineRadius = res.getDimensionPixelSize(
+                    Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+        }
+        setClipToOutline(mAlwaysRoundBothCorners);
+    }
+
+    public void setTopRoundness(float topRoundness, boolean animate) {
+        if (mTopRoundness != topRoundness) {
+            mTopRoundness = topRoundness;
+            PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+                    ROUNDNESS_PROPERTIES, animate);
+        }
+    }
+
+    protected void applyRoundness() {
+        invalidateOutline();
+        invalidate();
+    }
+
+    public float getCurrentBackgroundRadiusTop() {
+        return mCurrentTopRoundness * mOutlineRadius;
+    }
+
+    public float getCurrentTopRoundness() {
+        return mCurrentTopRoundness;
+    }
+
+    public float getCurrentBottomRoundness() {
+        return mCurrentBottomRoundness;
+    }
+
+    protected float getCurrentBackgroundRadiusBottom() {
+        return mCurrentBottomRoundness * mOutlineRadius;
+    }
+
+    public void setBottomRoundness(float bottomRoundness, boolean animate) {
+        if (mBottomRoundness != bottomRoundness) {
+            mBottomRoundness = bottomRoundness;
+            PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+                    ROUNDNESS_PROPERTIES, animate);
+        }
+    }
+
+    protected void setBackgroundTop(int backgroundTop) {
+        if (mBackgroundTop != backgroundTop) {
+            mBackgroundTop = backgroundTop;
+            invalidateOutline();
+        }
+    }
+
+    private void setTopRoundnessInternal(float topRoundness) {
+        mCurrentTopRoundness = topRoundness;
+        applyRoundness();
+    }
+
+    private void setBottomRoundnessInternal(float bottomRoundness) {
+        mCurrentBottomRoundness = bottomRoundness;
+        applyRoundness();
     }
 
     public void onDensityOrFontScaleChanged() {
         initDimens();
-        invalidateOutline();
+        applyRoundness();
     }
 
     @Override
     public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        int previousHeight = getActualHeight();
         super.setActualHeight(actualHeight, notifyListeners);
-        invalidateOutline();
+        if (previousHeight != actualHeight) {
+            applyRoundness();
+        }
     }
 
     @Override
     public void setClipTopAmount(int clipTopAmount) {
+        int previousAmount = getClipTopAmount();
         super.setClipTopAmount(clipTopAmount);
-        invalidateOutline();
+        if (previousAmount != clipTopAmount) {
+            applyRoundness();
+        }
     }
 
     @Override
     public void setClipBottomAmount(int clipBottomAmount) {
+        int previousAmount = getClipBottomAmount();
         super.setClipBottomAmount(clipBottomAmount);
-        invalidateOutline();
+        if (previousAmount != clipBottomAmount) {
+            applyRoundness();
+        }
     }
 
     protected void setOutlineAlpha(float alpha) {
         if (alpha != mOutlineAlpha) {
             mOutlineAlpha = alpha;
-            invalidateOutline();
+            applyRoundness();
         }
     }
 
@@ -113,8 +335,7 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            setClipToOutline(false);
-            invalidateOutline();
+            applyRoundness();
         }
     }
 
@@ -151,15 +372,22 @@
 
     protected void setOutlineRect(float left, float top, float right, float bottom) {
         mCustomOutline = true;
-        setClipToOutline(true);
 
         mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
 
         // Outlines need to be at least 1 dp
         mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
         mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-
-        invalidateOutline();
+        applyRoundness();
     }
 
+    public Path getCustomClipPath(View child) {
+        return null;
+    }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidateOutline();
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index aac9af8..18b9860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -202,6 +202,10 @@
         return mDark;
     }
 
+    public boolean isRemoved() {
+        return false;
+    }
+
     /**
      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc..68cf51c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,57 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
-import android.graphics.ColorFilter;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.systemui.R;
+
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
  */
 public class NotificationBackgroundView extends View {
 
+    private final boolean mDontModifyCorners;
     private Drawable mBackground;
     private int mClipTopAmount;
     private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
+    private float[] mCornerRadii = new float[8];
+    private int mCurrentSidePaddings;
+    private boolean mBottomIsRounded;
+    private int mBackgroundTop;
+    private boolean mBottomAmountClips = true;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mDontModifyCorners = getResources().getBoolean(
+                R.bool.config_clipNotificationsToOutline);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
-        draw(canvas, mBackground);
+        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+            canvas.save();
+            canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+            draw(canvas, mBackground);
+            canvas.restore();
+        }
     }
 
     private void draw(Canvas canvas, Drawable drawable) {
-        int bottom = mActualHeight - mClipBottomAmount;
-        if (drawable != null && bottom > mClipTopAmount) {
-            drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+        if (drawable != null) {
+            int bottom = mActualHeight;
+            if (mBottomIsRounded && mBottomAmountClips) {
+                bottom -= mClipBottomAmount;
+            }
+            drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+                    getWidth() - mCurrentSidePaddings, bottom);
             drawable.draw(canvas);
         }
     }
@@ -87,6 +107,7 @@
             unscheduleDrawable(mBackground);
         }
         mBackground = background;
+        mBackground.mutate();
         if (mBackground != null) {
             mBackground.setCallback(this);
             setTint(mTintColor);
@@ -94,6 +115,7 @@
         if (mBackground instanceof RippleDrawable) {
             ((RippleDrawable) mBackground).setForceSoftware(true);
         }
+        updateBackgroundRadii();
         invalidate();
     }
 
@@ -152,4 +174,45 @@
     public void setDrawableAlpha(int drawableAlpha) {
         mBackground.setAlpha(drawableAlpha);
     }
+
+    public void setRoundness(float topRoundness, float bottomRoundNess) {
+        mBottomIsRounded = bottomRoundNess != 0.0f;
+        mCornerRadii[0] = topRoundness;
+        mCornerRadii[1] = topRoundness;
+        mCornerRadii[2] = topRoundness;
+        mCornerRadii[3] = topRoundness;
+        mCornerRadii[4] = bottomRoundNess;
+        mCornerRadii[5] = bottomRoundNess;
+        mCornerRadii[6] = bottomRoundNess;
+        mCornerRadii[7] = bottomRoundNess;
+        updateBackgroundRadii();
+    }
+
+    public void setBottomAmountClips(boolean clips) {
+        if (clips != mBottomAmountClips) {
+            mBottomAmountClips = clips;
+            invalidate();
+        }
+    }
+
+    private void updateBackgroundRadii() {
+        if (mDontModifyCorners) {
+            return;
+        }
+        if (mBackground instanceof LayerDrawable) {
+            GradientDrawable gradientDrawable =
+                    (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+            gradientDrawable.setCornerRadii(mCornerRadii);
+        }
+    }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidate();
+    }
+
+    public void setBackgroundTop(int backgroundTop) {
+        mBackgroundTop = backgroundTop;
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c89..39c2131 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,8 +35,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -49,6 +50,7 @@
  */
 public class NotificationContentView extends FrameLayout {
 
+    private static final String TAG = "NotificationContentView";
     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     public static final int VISIBLE_TYPE_EXPANDED = 1;
     public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +60,9 @@
     public static final int UNDEFINED = -1;
 
     private final Rect mClipBounds = new Rect();
-    private final int mMinContractedHeight;
-    private final int mNotificationContentMarginEnd;
 
+    private int mMinContractedHeight;
+    private int mNotificationContentMarginEnd;
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
@@ -134,15 +136,22 @@
     private int mClipBottomAmount;
     private boolean mIsLowPriority;
     private boolean mIsContentExpandable;
+    private int mCustomViewSidePaddings;
 
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext(), this);
+        initView();
+    }
+
+    public void initView() {
         mMinContractedHeight = getResources().getDimensionPixelSize(
                 R.dimen.min_notification_layout_height);
         mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_content_margin_end);
+        mCustomViewSidePaddings = getResources().getDimensionPixelSize(
+                R.dimen.notification_content_custom_view_side_padding);
     }
 
     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -178,7 +187,7 @@
                     : MeasureSpec.makeMeasureSpec(size, useExactly
                             ? MeasureSpec.EXACTLY
                             : MeasureSpec.AT_MOST);
-            mExpandedChild.measure(widthMeasureSpec, spec);
+            measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
         }
         if (mContractedChild != null) {
@@ -196,22 +205,22 @@
             } else {
                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
             }
-            mContractedChild.measure(widthMeasureSpec, heightSpec);
+            measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             int measuredHeight = mContractedChild.getMeasuredHeight();
             if (measuredHeight < mMinContractedHeight) {
                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
-                mContractedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
             if (updateContractedHeaderWidth()) {
-                mContractedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
             if (mExpandedChild != null
                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
                         MeasureSpec.EXACTLY);
-                mExpandedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
         }
         if (mHeadsUpChild != null) {
@@ -223,9 +232,9 @@
                 size = Math.min(size, layoutParams.height);
                 useExactly = true;
             }
-            mHeadsUpChild.measure(widthMeasureSpec,
+            measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
-                            : MeasureSpec.AT_MOST));
+                            : MeasureSpec.AT_MOST), 0);
             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
         }
         if (mSingleLineView != null) {
@@ -382,6 +391,38 @@
         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+        updateMargins(child);
+    }
+
+    private void updateMargins(View child) {
+        if (child == null) {
+            return;
+        }
+        NotificationViewWrapper wrapper = getWrapperForView(child);
+        boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper;
+        boolean needsMargins = isCustomView &&
+                child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P;
+        int padding = needsMargins ? mCustomViewSidePaddings : 0;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+        layoutParams.setMarginStart(padding);
+        layoutParams.setMarginEnd(padding);
+        child.setLayoutParams(layoutParams);
+    }
+
+    private NotificationViewWrapper getWrapperForView(View child) {
+        if (child == mContractedChild) {
+            return mContractedWrapper;
+        }
+        if (child == mExpandedChild) {
+            return mExpandedWrapper;
+        }
+        if (child == mHeadsUpChild) {
+            return mHeadsUpWrapper;
+        }
+        if (child == mAmbientChild) {
+            return mAmbientWrapper;
+        }
+        return null;
     }
 
     public void setExpandedChild(View child) {
@@ -415,6 +456,7 @@
         mExpandedChild = child;
         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+        updateMargins(child);
     }
 
     public void setHeadsUpChild(View child) {
@@ -448,6 +490,7 @@
         mHeadsUpChild = child;
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+        updateMargins(child);
     }
 
     public void setAmbientChild(View child) {
@@ -643,6 +686,13 @@
         int endHeight = getViewForVisibleType(mVisibleType).getHeight();
         int progress = Math.abs(mContentHeight - startHeight);
         int totalDistance = Math.abs(endHeight - startHeight);
+        if (totalDistance == 0) {
+            Log.wtf(TAG, "the total transformation distance is 0"
+                    + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+                    + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+                    + "\n mContentHeight: " + mContentHeight);
+            return 1.0f;
+        }
         float amount = (float) progress / (float) totalDistance;
         return Math.min(1.0f, amount);
     }
@@ -1459,4 +1509,20 @@
         }
         return false;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+        if (mUserExpanding) {
+             needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+        }
+        return needsPaddings;
+    }
+
+    private boolean shouldClipToSidePaddings(int visibleType) {
+        NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+        if (visibleWrapper == null) {
+            return false;
+        }
+        return visibleWrapper.shouldClipToSidePaddings();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b07..b2604fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@
     private float mHorizSpaceForIcon = -1;
     private int mVertSpaceForIcons = -1;
     private int mIconPadding = -1;
+    private int mSidePadding;
 
     private float mAlpha = 0f;
     private float mPrevX;
@@ -175,6 +176,7 @@
         final Resources res = mContext.getResources();
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
         mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
         mMenuItems.clear();
         // Construct the menu items based on the notification
@@ -496,8 +498,8 @@
         final int count = mMenuContainer.getChildCount();
         for (int i = 0; i < count; i++) {
             final View v = mMenuContainer.getChildAt(i);
-            final float left = i * mHorizSpaceForIcon;
-            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+            final float left = mSidePadding + i * mHorizSpaceForIcon;
+            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
             v.setX(showOnLeft ? left : right);
         }
         mOnLeft = showOnLeft;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde..b7a00eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -85,6 +85,7 @@
     private boolean mVibrationOnAnimation;
     private boolean mUserTouchingScreen;
     private boolean mTouchActive;
+    private float mFirstElementRoundness;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -107,6 +108,7 @@
         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                 NotificationPanelView.DOZE_ANIMATION_DURATION);
         mShelfState = new ShelfState();
+        setBottomRoundness(1.0f, false /* animate */);
         initDimens();
     }
 
@@ -252,6 +254,8 @@
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
+        int backgroundTop = 0;
+        float firstElementRoundness = 0.0f;
         while (notificationIndex < mHostLayout.getChildCount()) {
             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
             notificationIndex++;
@@ -302,9 +306,20 @@
             if (notGoneIndex != 0 || !aboveShelf) {
                 row.setAboveShelf(false);
             }
+            if (notGoneIndex == 0) {
+                StatusBarIconView icon = row.getEntry().expandedIcon;
+                NotificationIconContainer.IconState iconState = getIconState(icon);
+                if (iconState.clampedAppearAmount == 1.0f) {
+                    // only if the first icon is fully in the shelf we want to clip to it!
+                    backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+                    firstElementRoundness = row.getCurrentTopRoundness();
+                }
+            }
             notGoneIndex++;
             previousColor = ownColorUntinted;
         }
+        setBackgroundTop(backgroundTop);
+        setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
@@ -325,6 +340,13 @@
         }
     }
 
+    private void setFirstElementRoundness(float firstElementRoundness) {
+        if (mFirstElementRoundness != firstElementRoundness) {
+            mFirstElementRoundness = firstElementRoundness;
+            setTopRoundness(firstElementRoundness, false /* animate */);
+        }
+    }
+
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c..ff6c775 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,7 +19,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
 import android.util.ArrayMap;
@@ -38,11 +37,11 @@
             = new ArrayList<>();
     private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
     private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
-    private final HeadsUpManager mHeadsUpManager;
+    private final Delegate mDelegate;
 
-    public RemoteInputController(HeadsUpManager headsUpManager) {
+    public RemoteInputController(Delegate delegate) {
         addCallback(Dependency.get(StatusBarWindowManager.class));
-        mHeadsUpManager = headsUpManager;
+        mDelegate = delegate;
     }
 
     /**
@@ -114,7 +113,7 @@
     }
 
     private void apply(NotificationData.Entry entry) {
-        mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+        mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
         boolean remoteInputActive = isRemoteInputActive();
         int N = mCallbacks.size();
         for (int i = 0; i < N; i++) {
@@ -204,9 +203,35 @@
         }
     }
 
+    public void requestDisallowLongPressAndDismiss() {
+        mDelegate.requestDisallowLongPressAndDismiss();
+    }
+
+    public void lockScrollTo(NotificationData.Entry entry) {
+        mDelegate.lockScrollTo(entry);
+    }
+
     public interface Callback {
         default void onRemoteInputActive(boolean active) {}
 
         default void onRemoteInputSent(NotificationData.Entry entry) {}
     }
+
+    public interface Delegate {
+        /**
+         * Activate remote input if necessary.
+         */
+        void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+       /**
+        * Request that the view does not dismiss nor perform long press for the current touch.
+        */
+       void requestDisallowLongPressAndDismiss();
+
+      /**
+       * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+       * the user scrolls, or {@param v} loses focus or is detached.
+       */
+       void lockScrollTo(NotificationData.Entry entry);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index a53e348..8830352 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
@@ -50,6 +51,7 @@
 public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
     private static final String TAG = "ScrimView";
     private final ColorExtractor.GradientColors mColors;
+    private int mDensity;
     private boolean mDrawAsSrc;
     private float mViewAlpha = 1.0f;
     private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@
         }
     };
     private Runnable mChangeRunnable;
+    private int mCornerRadius;
 
     public ScrimView(Context context) {
         this(context, null);
@@ -93,6 +96,24 @@
         mColors = new ColorExtractor.GradientColors();
         updateScreenSize();
         updateColorWithTint(false);
+        initView();
+        final Configuration currentConfig = mContext.getResources().getConfiguration();
+        mDensity = currentConfig.densityDpi;
+    }
+
+    private void initView() {
+        mCornerRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        int densityDpi = newConfig.densityDpi;
+        if (mDensity != densityDpi) {
+            mDensity = densityDpi;
+            initView();
+        }
     }
 
     @Override
@@ -145,6 +166,28 @@
                     mDrawable.draw(canvas);
                     canvas.restore();
                 }
+                // We also need to draw the rounded corners of the background
+                canvas.save();
+                canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+                        mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+                        mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+                        mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.right - mCornerRadius,
+                        mExcludedRect.bottom - mCornerRadius,
+                        mExcludedRect.right, mExcludedRect.bottom);
+                mDrawable.draw(canvas);
+                canvas.restore();
             }
         }
     }
@@ -252,6 +295,13 @@
         return false;
     }
 
+    /**
+     * It might look counterintuitive to have another method to set the alpha instead of
+     * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+     * optimizing blend modes, so it makes sense.
+     *
+     * @param alpha Gradient alpha from 0 to 1.
+     */
     public void setViewAlpha(float alpha) {
         if (alpha != mViewAlpha) {
             mViewAlpha = alpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index f5c77f2..64c52ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -369,7 +369,7 @@
     private void onFacetClicked(Intent intent, int index) {
         String packageName = intent.getPackage();
 
-        if (packageName == null) {
+        if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 0000000..d7b211f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+    int getAnimationStartTag();
+
+    int getAnimationEndTag();
+
+    int getAnimatorTag();
+
+    Property getProperty();
+
+    static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+            Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+        Property<T, Float> property = new FloatProperty<T>(name) {
+
+            @Override
+            public Float get(T object) {
+                return getter.apply(object);
+            }
+
+            @Override
+            public void setValue(T object, float value) {
+                setter.accept(object, value);
+            }
+        };
+        return new AnimatableProperty() {
+            @Override
+            public int getAnimationStartTag() {
+                return startValueTag;
+            }
+
+            @Override
+            public int getAnimationEndTag() {
+                return endValueTag;
+            }
+
+            @Override
+            public int getAnimatorTag() {
+                return animatorTag;
+            }
+
+            @Override
+            public Property getProperty() {
+                return property;
+            }
+        };
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index fc420eb..27defca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -89,6 +89,7 @@
 
     private void transformViewInternal(MessagingLayoutTransformState mlt,
             float transformationAmount, boolean to) {
+        ensureVisible();
         ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
                 mMessagingLayout.getMessagingGroups());
         ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
@@ -332,6 +333,7 @@
 
     @Override
     public void setVisible(boolean visible, boolean force) {
+        super.setVisible(visible, force);
         resetTransformedView();
         ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
         for (int i = 0; i < ownGroups.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..66682e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
+import android.os.Build;
 import android.view.View;
 
 import com.android.systemui.R;
@@ -37,6 +38,7 @@
     private final Paint mGreyPaint = new Paint();
     private boolean mIsLegacy;
     private int mLegacyColor;
+    private boolean mBeforeP;
 
     protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -115,4 +117,17 @@
         super.setLegacy(legacy);
         mIsLegacy = legacy;
     }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        // Before P we ensure that they are now drawing inside out content bounds since we inset
+        // the view. If they target P, then we don't have that guarantee and we need to be safe.
+        return !mBeforeP;
+    }
+
+    @Override
+    public void onContentUpdated(ExpandableNotificationRow row) {
+        super.onContentUpdated(row);
+        mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a1..060e6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -58,6 +58,11 @@
 
     @Override
     public boolean isDimmable() {
-        return false;
+        return getCustomBackgroundColor() == 0;
+    }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index fd085d9..e07112f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@
         updateActionOffset();
     }
 
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+    }
+
     private void updateActionOffset() {
         if (mActionsContainer != null) {
             // We should never push the actions higher than they are in the headsup view.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 1cd5f15..8a767bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@
     public int getMinLayoutHeight() {
         return 0;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba943..92dcc9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@
  */
 public class PropertyAnimator {
 
+    public static <T extends View> void setProperty(final T view,
+            AnimatableProperty animatableProperty, float newEndValue,
+            AnimationProperties properties, boolean animated) {
+        int animatorTag = animatableProperty.getAnimatorTag();
+        ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+        if (previousAnimator != null || animated) {
+            startAnimation(view, animatableProperty, newEndValue, properties);
+        } else {
+            // no new animation needed, let's just apply the value
+            animatableProperty.getProperty().set(view, newEndValue);
+        }
+    }
+
     public static <T extends View> void startAnimation(final T view,
             AnimatableProperty animatableProperty, float newEndValue,
             AnimationProperties properties) {
@@ -102,10 +115,4 @@
         view.setTag(animationEndTag, newEndValue);
     }
 
-    public interface AnimatableProperty {
-        int getAnimationStartTag();
-        int getAnimationEndTag();
-        int getAnimatorTag();
-        Property getProperty();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index ad07af0..dec5303 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,18 +95,22 @@
     public void transformViewFrom(TransformState otherState, float transformationAmount) {
         mTransformedView.animate().cancel();
         if (sameAs(otherState)) {
-            if (mTransformedView.getVisibility() == View.INVISIBLE
-                    || mTransformedView.getAlpha() != 1.0f) {
-                // We have the same content, lets show ourselves
-                mTransformedView.setAlpha(1.0f);
-                mTransformedView.setVisibility(View.VISIBLE);
-            }
+            ensureVisible();
         } else {
             CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
         }
         transformViewFullyFrom(otherState, transformationAmount);
     }
 
+    protected void ensureVisible() {
+        if (mTransformedView.getVisibility() == View.INVISIBLE
+                || mTransformedView.getAlpha() != 1.0f) {
+            // We have the same content, lets show ourselves
+            mTransformedView.setAlpha(1.0f);
+            mTransformedView.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
         transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b..3f57c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@
     public void dump(PrintWriter pw) {
         pw.println("  DozeParameters:");
         pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
-        pw.print("    getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
-        pw.print("    getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
-        pw.print("    getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
-        pw.print("    getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+        pw.print("    getPulseDuration(): "); pw.println(getPulseDuration());
+        pw.print("    getPulseInDuration(): "); pw.println(getPulseInDuration());
         pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
         pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
         pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@
         return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
     }
 
-    public int getPulseDuration(boolean pickup) {
-        return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+    public int getPulseDuration() {
+        return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
     }
 
-    public int getPulseInDuration(boolean pickupOrDoubleTap) {
-        return pickupOrDoubleTap
-                ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
-                : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+    public int getPulseInDuration() {
+        return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
     }
 
     public int getPulseVisibleDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb849..1011383 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
-import android.view.animation.Interpolator;
 
-import com.android.systemui.Interpolators;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 
@@ -40,74 +35,59 @@
     private final Handler mHandler = new Handler();
     private final ScrimController mScrimController;
 
-    private final Context mContext;
-
     private boolean mDozing;
     private DozeHost.PulseCallback mPulseCallback;
     private int mPulseReason;
-    private Animator mInFrontAnimator;
-    private Animator mBehindAnimator;
-    private float mInFrontTarget;
-    private float mBehindTarget;
-    private boolean mDozingAborted;
-    private boolean mWakeAndUnlocking;
     private boolean mFullyPulsing;
 
-    private float mAodFrontScrimOpacity = 0;
-    private Runnable mSetDozeInFrontAlphaDelayed;
+    private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+        @Override
+        public void onDisplayBlanked() {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+                        + DozeLog.pulseReasonToString(mPulseReason));
+            }
+            if (!mDozing) {
+                return;
+            }
+
+            // Signal that the pulse is ready to turn the screen on and draw.
+            pulseStarted();
+        }
+
+        @Override
+        public void onFinished() {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+            }
+            if (!mDozing) {
+                return;
+            }
+            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+            mHandler.postDelayed(mPulseOutExtended,
+                    mDozeParameters.getPulseVisibleDurationExtended());
+            mFullyPulsing = true;
+        }
+
+        /**
+         * Transition was aborted before it was over.
+         */
+        @Override
+        public void onCancelled() {
+            pulseFinished();
+        }
+    };
 
     public DozeScrimController(ScrimController scrimController, Context context) {
-        mContext = context;
         mScrimController = scrimController;
         mDozeParameters = new DozeParameters(context);
     }
 
-    public void setDozing(boolean dozing, boolean animate) {
+    public void setDozing(boolean dozing) {
         if (mDozing == dozing) return;
         mDozing = dozing;
-        mWakeAndUnlocking = false;
-        if (mDozing) {
-            mDozingAborted = false;
-            abortAnimations();
-            mScrimController.setDozeBehindAlpha(1f);
-            setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
-        } else {
+        if (!mDozing) {
             cancelPulsing();
-            if (animate) {
-                startScrimAnimation(false /* inFront */, 0f /* target */,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION,
-                        Interpolators.LINEAR_OUT_SLOW_IN);
-                startScrimAnimation(true /* inFront */, 0f /* target */,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION,
-                        Interpolators.LINEAR_OUT_SLOW_IN);
-            } else {
-                abortAnimations();
-                mScrimController.setDozeBehindAlpha(0f);
-                setDozeInFrontAlpha(0f);
-            }
-        }
-    }
-
-    /**
-     * Set the opacity of the front scrim when showing AOD1
-     *
-     * Used to emulate lower brightness values than the hardware supports natively.
-     */
-    public void setAodDimmingScrim(float scrimOpacity) {
-        mAodFrontScrimOpacity = scrimOpacity;
-        if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
-                && mDozeParameters.getAlwaysOn()) {
-            setDozeInFrontAlpha(mAodFrontScrimOpacity);
-        }
-    }
-
-    public void setWakeAndUnlocking() {
-        // Immediately abort the doze scrims in case of wake-and-unlock
-        // for pulsing so the Keyguard fade-out animation scrim can take over.
-        if (!mWakeAndUnlocking) {
-            mWakeAndUnlocking = true;
-            mScrimController.setDozeBehindAlpha(0f);
-            setDozeInFrontAlpha(0f);
         }
     }
 
@@ -118,37 +98,21 @@
         }
 
         if (!mDozing || mPulseCallback != null) {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+                        + (mPulseCallback != null));
+            }
             // Pulse suppressed.
             callback.onPulseFinished();
             return;
         }
 
-        // Begin pulse.  Note that it's very important that the pulse finished callback
+        // Begin pulse. Note that it's very important that the pulse finished callback
         // be invoked when we're done so that the caller can drop the pulse wakelock.
         mPulseCallback = callback;
         mPulseReason = reason;
-        setDozeInFrontAlpha(1f);
-        mHandler.post(mPulseIn);
-    }
 
-    /**
-     * Aborts pulsing immediately.
-     */
-    public void abortPulsing() {
-        cancelPulsing();
-        if (mDozing && !mWakeAndUnlocking) {
-            mScrimController.setDozeBehindAlpha(1f);
-            setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
-                    ? mAodFrontScrimOpacity : 1f);
-        }
-    }
-
-    /**
-     * Aborts dozing immediately.
-     */
-    public void abortDoze() {
-        mDozingAborted = true;
-        abortPulsing();
+        mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
     }
 
     public void pulseOutNow() {
@@ -157,17 +121,6 @@
         }
     }
 
-    public void onScreenTurnedOn() {
-        if (isPulsing()) {
-            final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
-                    || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
-            startScrimAnimation(true /* inFront */, 0f,
-                    mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
-                    pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
-                    mPulseInFinished);
-        }
-    }
-
     public boolean isPulsing() {
         return mPulseCallback != null;
     }
@@ -181,11 +134,9 @@
     }
 
     private void cancelPulsing() {
-        if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
         if (mPulseCallback != null) {
+            if (DEBUG) Log.d(TAG, "Cancel pulsing");
             mFullyPulsing = false;
-            mHandler.removeCallbacks(mPulseIn);
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
             pulseFinished();
@@ -193,151 +144,20 @@
     }
 
     private void pulseStarted() {
+        DozeLog.tracePulseStart(mPulseReason);
         if (mPulseCallback != null) {
             mPulseCallback.onPulseStarted();
         }
     }
 
     private void pulseFinished() {
+        DozeLog.tracePulseFinish();
         if (mPulseCallback != null) {
             mPulseCallback.onPulseFinished();
             mPulseCallback = null;
         }
     }
 
-    private void abortAnimations() {
-        if (mInFrontAnimator != null) {
-            mInFrontAnimator.cancel();
-        }
-        if (mBehindAnimator != null) {
-            mBehindAnimator.cancel();
-        }
-    }
-
-    private void startScrimAnimation(final boolean inFront, float target, long duration,
-            Interpolator interpolator) {
-        startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
-    }
-
-    private void startScrimAnimation(final boolean inFront, float target, long duration,
-            Interpolator interpolator, final Runnable endRunnable) {
-        Animator current = getCurrentAnimator(inFront);
-        if (current != null) {
-            float currentTarget = getCurrentTarget(inFront);
-            if (currentTarget == target) {
-                return;
-            }
-            current.cancel();
-        }
-        ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
-        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                setDozeAlpha(inFront, value);
-            }
-        });
-        anim.setInterpolator(interpolator);
-        anim.setDuration(duration);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                setCurrentAnimator(inFront, null);
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-            }
-        });
-        anim.start();
-        setCurrentAnimator(inFront, anim);
-        setCurrentTarget(inFront, target);
-    }
-
-    private float getCurrentTarget(boolean inFront) {
-        return inFront ? mInFrontTarget : mBehindTarget;
-    }
-
-    private void setCurrentTarget(boolean inFront, float target) {
-        if (inFront) {
-            mInFrontTarget = target;
-        } else {
-            mBehindTarget = target;
-        }
-    }
-
-    private Animator getCurrentAnimator(boolean inFront) {
-        return inFront ? mInFrontAnimator : mBehindAnimator;
-    }
-
-    private void setCurrentAnimator(boolean inFront, Animator animator) {
-        if (inFront) {
-            mInFrontAnimator = animator;
-        } else {
-            mBehindAnimator = animator;
-        }
-    }
-
-    private void setDozeAlpha(boolean inFront, float alpha) {
-        if (mWakeAndUnlocking) {
-            return;
-        }
-        if (inFront) {
-            mScrimController.setDozeInFrontAlpha(alpha);
-        } else {
-            mScrimController.setDozeBehindAlpha(alpha);
-        }
-    }
-
-    private float getDozeAlpha(boolean inFront) {
-        return inFront
-                ? mScrimController.getDozeInFrontAlpha()
-                : mScrimController.getDozeBehindAlpha();
-    }
-
-    private void setDozeInFrontAlpha(float opacity) {
-        setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
-    }
-
-    private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
-        if (mSetDozeInFrontAlphaDelayed != null) {
-            mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
-            mSetDozeInFrontAlphaDelayed = null;
-        }
-        if (delayMs <= 0) {
-            mScrimController.setDozeInFrontAlpha(opacity);
-        } else {
-            mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
-                setDozeInFrontAlpha(opacity);
-            }, delayMs);
-        }
-    }
-
-    private final Runnable mPulseIn = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
-                    + DozeLog.pulseReasonToString(mPulseReason));
-            if (!mDozing) return;
-            DozeLog.tracePulseStart(mPulseReason);
-
-            // Signal that the pulse is ready to turn the screen on and draw.
-            pulseStarted();
-        }
-    };
-
-    private final Runnable mPulseInFinished = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
-            if (!mDozing) return;
-            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
-            mHandler.postDelayed(mPulseOutExtended,
-                    mDozeParameters.getPulseVisibleDurationExtended());
-            mFullyPulsing = true;
-        }
-    };
-
     private final Runnable mPulseOutExtended = new Runnable() {
         @Override
         public void run() {
@@ -354,38 +174,13 @@
             mHandler.removeCallbacks(mPulseOutExtended);
             if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
             if (!mDozing) return;
-            startScrimAnimation(true /* inFront */, 1,
-                    mDozeParameters.getPulseOutDuration(),
-                    Interpolators.ALPHA_IN, mPulseOutFinishing);
+            mScrimController.transitionTo(ScrimState.AOD,
+                    new ScrimController.Callback() {
+                        @Override
+                        public void onDisplayBlanked() {
+                            pulseFinished();
+                        }
+                    });
         }
     };
-
-    private final Runnable mPulseOutFinishing = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse out finished");
-            DozeLog.tracePulseFinish();
-            if (mDozeParameters.getAlwaysOn() && mDozing) {
-                // Setting power states can block rendering. For AOD, delay finishing the pulse and
-                // setting the power state until the fully black scrim had time to hit the
-                // framebuffer.
-                mHandler.postDelayed(mPulseOutFinished, 30);
-            } else {
-                mPulseOutFinished.run();
-            }
-        }
-    };
-
-    private final Runnable mPulseOutFinished = new Runnable() {
-        @Override
-        public void run() {
-            // Signal that the pulse is all finished so we can turn the screen off now.
-            DozeScrimController.this.pulseFinished();
-            if (mDozeParameters.getAlwaysOn()) {
-                // Setting power states can happen after we push out the frame. Make sure we
-                // stay fully opaque until the power state request reaches the lower levels.
-                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
-            }
-        }
-    };
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369db..80d4061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@
     }
 
     private boolean pulsingOrAod() {
-        boolean pulsing = mDozeScrimController.isPulsing();
-        boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
-        return pulsing || dozingWithScreenOn;
+        final ScrimState scrimState = mScrimController.getState();
+        return scrimState == ScrimState.AOD
+                || scrimState == ScrimState.PULSING;
     }
 
     @Override
@@ -246,15 +246,12 @@
                             true /* allowEnterAnimation */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
-                    mDozeScrimController.abortDoze();
                 } else {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
                     mUpdateMonitor.awakenFromDream();
                 }
                 mStatusBarWindowManager.setStatusBarFocusable(false);
                 mKeyguardViewMediator.onWakeAndUnlocking();
-                mScrimController.setWakeAndUnlocking();
-                mDozeScrimController.setWakeAndUnlocking();
                 if (mStatusBar.getNavigationBarView() != null) {
                     mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
                 }
@@ -269,6 +266,7 @@
     }
 
     private void showBouncer() {
+        mScrimController.transitionTo(ScrimState.BOUNCER);
         mStatusBarKeyguardViewManager.animateCollapsePanels(
                 FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
         mPendingShowBouncer = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 5c9446ce86..34486db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@
                 }
                 break;
             case STATE_FACE_UNLOCK:
-                iconRes = com.android.internal.R.drawable.ic_account_circle;
+                iconRes = R.drawable.ic_account_circle;
                 break;
             case STATE_FINGERPRINT:
                 // If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index c950036..b81a3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -80,7 +80,8 @@
 
     @Override
     protected boolean isLightsOut(int mode) {
-        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
+        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
+                && mode != MODE_WARNING);
     }
 
     public LightBarTransitionsController getLightTransitionsController() {
@@ -108,7 +109,9 @@
         // ok, everyone, stop it right there
         navButtons.animate().cancel();
 
-        final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
+        // Bump percentage by 10% if dark.
+        float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10;
+        final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
 
         if (!animate) {
             navButtons.setAlpha(navButtonsAlpha);
@@ -130,6 +133,9 @@
         for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
             buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
         }
+        if (mAutoDim) {
+            applyLightsOut(false, true);
+        }
     }
 
     private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c6..836efff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -209,7 +209,7 @@
                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
-        if (mDark && child instanceof StatusBarIconView) {
+        if (child instanceof StatusBarIconView) {
             ((StatusBarIconView) child).setDark(mDark, false, 0);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index fff6abe..17e3599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -239,6 +239,7 @@
     private ValueAnimator mDarkAnimator;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private boolean mUserSetupComplete;
+    private int mQsNotificationTopPadding;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -307,6 +308,8 @@
                 R.dimen.max_notification_fadeout_height);
         mIndicationBottomPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_bottom_padding);
+        mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.qs_notification_keyguard_padding);
     }
 
     public void updateResources() {
@@ -818,7 +821,7 @@
 
     private float getQsExpansionFraction() {
         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
-                / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+                / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
     }
 
     @Override
@@ -1361,7 +1364,7 @@
             // take the maximum and linearly interpolate with the panel expansion for a nice motion.
             int maxNotifications = mClockPositionResult.stackScrollerPadding
                     - mClockPositionResult.stackScrollerPaddingAdjustment;
-            int maxQs = getTempQsMaxExpansion();
+            int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
             int max = mStatusBarState == StatusBarState.KEYGUARD
                     ? Math.max(maxNotifications, maxQs)
                     : maxQs;
@@ -1375,7 +1378,7 @@
             // from a scrolled quick settings.
             return interpolate(getQsExpansionFraction(),
                     mNotificationStackScroller.getIntrinsicPadding(),
-                    mQsMaxExpansionHeight);
+                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
         } else {
             return mQsExpansionHeight;
         }
@@ -1544,7 +1547,7 @@
                         / (panelHeightQsExpanded - panelHeightQsCollapsed);
             }
             setQsExpansion(mQsMinExpansionHeight
-                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+                    + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
         }
         updateExpandedHeight(expandedHeight);
         updateHeader();
@@ -1566,14 +1569,6 @@
         }
     }
 
-    /**
-     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
-     *         collapsing QS / the panel when QS was scrolled
-     */
-    private int getTempQsMaxExpansion() {
-        return mQsMaxExpansionHeight;
-    }
-
     private int calculatePanelHeightShade() {
         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1591,10 @@
         }
         int maxQsHeight = mQsMaxExpansionHeight;
 
+        if (mKeyguardShowing) {
+            maxQsHeight += mQsNotificationTopPadding;
+        }
+
         // If an animation is changing the size of the QS panel, take the animated value.
         if (mQsSizeChangeAnimator != null) {
             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3..dfd4c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.os.Trace;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,12 +36,14 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
 import com.android.internal.graphics.ColorUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
@@ -56,33 +63,54 @@
  * security method gets shown).
  */
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
-        OnHeadsUpChangedListener, OnColorsChangedListener {
+        OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+    private static final String TAG = "ScrimController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     public static final long ANIMATION_DURATION = 220;
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
             = new PathInterpolator(0f, 0, 0.7f, 1f);
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
-    // Default alpha value for most scrims, if unsure use this constant
+    /**
+     * Default alpha value for most scrims.
+     */
     public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
-    // A scrim varies its opacity based on a busyness factor, for example
-    // how many notifications are currently visible.
+    /**
+     * A scrim varies its opacity based on a busyness factor, for example
+     * how many notifications are currently visible.
+     */
     public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+    /**
+     * The most common scrim, the one under the keyguard.
+     */
     protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+    /**
+     * We fade out the bottom scrim when the bouncer is visible.
+     */
     protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
-    private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
-    private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
-    private static final int TAG_KEY_ANIM = R.id.scrim;
+    /**
+     * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+     */
+    protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+    static final int TAG_KEY_ANIM = R.id.scrim;
+    static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
     private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
     private static final float NOT_INITIALIZED = -1;
 
-    private final LightBarController mLightBarController;
+    private ScrimState mState = ScrimState.UNINITIALIZED;
+    private final Context mContext;
     protected final ScrimView mScrimBehind;
     protected final ScrimView mScrimInFront;
-    private final UnlockMethodCache mUnlockMethodCache;
     private final View mHeadsUpScrim;
+    private final LightBarController mLightBarController;
+    private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final DozeParameters mDozeParameters;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -94,61 +122,53 @@
     protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
     protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
 
-    protected boolean mKeyguardShowing;
     private float mFraction;
 
     private boolean mDarkenWhileDragging;
-    protected boolean mBouncerShowing;
-    protected boolean mBouncerIsKeyguard = false;
-    private boolean mWakeAndUnlocking;
     protected boolean mAnimateChange;
     private boolean mUpdatePending;
     private boolean mTracking;
     private boolean mAnimateKeyguardFadingOut;
-    protected long mDurationOverride = -1;
+    protected long mAnimationDuration = -1;
     private long mAnimationDelay;
     private Runnable mOnAnimationFinished;
     private boolean mDeferFinishedListener;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
-    private boolean mDozing;
-    private float mDozeInFrontAlpha;
-    private float mDozeBehindAlpha;
     private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
     private float mCurrentBehindAlpha = NOT_INITIALIZED;
-    private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+    private int mCurrentInFrontTint;
+    private int mCurrentBehindTint;
     private int mPinnedHeadsUpCount;
     private float mTopHeadsUpDragAmount;
     private View mDraggedHeadsUpView;
-    private boolean mForceHideScrims;
-    private boolean mSkipFirstFrame;
-    private boolean mDontAnimateBouncerChanges;
     private boolean mKeyguardFadingOutInProgress;
-    private boolean mAnimatingDozeUnlock;
     private ValueAnimator mKeyguardFadeoutAnimation;
-    /** Wake up from AOD transition is starting; need fully opaque front scrim */
-    private boolean mWakingUpFromAodStarting;
-    /** Wake up from AOD transition is in progress; need black tint */
-    private boolean mWakingUpFromAodInProgress;
-    /** Wake up from AOD transition is animating; need to reset when animation finishes */
-    private boolean mWakingUpFromAodAnimationRunning;
-    private boolean mScrimsVisble;
+    private boolean mScrimsVisible;
     private final Consumer<Boolean> mScrimVisibleListener;
+    private boolean mBlankScreen;
+    private boolean mScreenBlankingCallbackCalled;
+    private Callback mCallback;
+
+    private final WakeLock mWakeLock;
+    private boolean mWakeLockHeld;
 
     public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
-            ScrimView scrimInFront, View headsUpScrim,
-            Consumer<Boolean> scrimVisibleListener) {
+            ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+            DozeParameters dozeParameters) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mHeadsUpScrim = headsUpScrim;
         mScrimVisibleListener = scrimVisibleListener;
-        final Context context = scrimBehind.getContext();
-        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mContext = scrimBehind.getContext();
+        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mLightBarController = lightBarController;
-        mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mWakeLock = createWakeLock();
         // Scrim alpha is initially set to the value on the resource but might be changed
         // to make sure that text on top of it is legible.
         mScrimBehindAlpha = mScrimBehindAlphaResValue;
+        mDozeParameters = dozeParameters;
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
@@ -158,22 +178,90 @@
                 ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mNeedsDrawableColorUpdate = true;
 
+        final ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+            states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+        }
+        mState = ScrimState.UNINITIALIZED;
+
         updateHeadsUpScrim(false);
         updateScrims();
     }
 
-    public void setKeyguardShowing(boolean showing) {
-        mKeyguardShowing = showing;
+    public void transitionTo(ScrimState state) {
+        transitionTo(state, null);
+    }
 
-        // Showing/hiding the keyguard means that scrim colors have to be switched
-        mNeedsDrawableColorUpdate = true;
-        scheduleUpdate();
+    public void transitionTo(ScrimState state, Callback callback) {
+        if (state == mState) {
+            return;
+        } else if (DEBUG) {
+            Log.d(TAG, "State changed to: " + state);
+        }
+
+        if (state == ScrimState.UNINITIALIZED) {
+            throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+        }
+
+        if (mCallback != null) {
+            mCallback.onCancelled();
+        }
+        mCallback = callback;
+
+        state.prepare(mState);
+        mScreenBlankingCallbackCalled = false;
+        mAnimationDelay = 0;
+        mBlankScreen = state.getBlanksScreen();
+        mAnimateChange = state.getAnimateChange();
+        mAnimationDuration = state.getAnimationDuration();
+        mCurrentInFrontTint = state.getFrontTint();
+        mCurrentBehindTint = state.getBehindTint();
+        mCurrentInFrontAlpha = state.getFrontAlpha();
+        mCurrentBehindAlpha = state.getBehindAlpha();
+
+        // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+        // to do the same when you're just showing the brightness mirror.
+        mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+        if (mKeyguardFadeoutAnimation != null) {
+            mKeyguardFadeoutAnimation.cancel();
+        }
+
+        mState = state;
+
+        // Do not let the device sleep until we're done with all animations
+        if (!mWakeLockHeld) {
+            if (mWakeLock != null) {
+                mWakeLockHeld = true;
+                mWakeLock.acquire();
+            } else {
+                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+            }
+        }
+
+        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
+            scheduleUpdate();
+        } else {
+            // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+            // with too many things at this case, in order to not skip the initial frames.
+            mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+            mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+        }
+    }
+
+    public ScrimState getState() {
+        return mState;
     }
 
     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
             float scrimBehindAlphaUnlocking) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
         mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+        }
         scheduleUpdate();
     }
 
@@ -186,131 +274,59 @@
         mTracking = false;
     }
 
+    /**
+     * Current state of the shade expansion when pulling it from the top.
+     * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+     *
+     * The expansion fraction is tied to the scrim opacity.
+     *
+     * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+     */
     public void setPanelExpansion(float fraction) {
         if (mFraction != fraction) {
             mFraction = fraction;
-            scheduleUpdate();
+
+            if (mState == ScrimState.UNLOCKED) {
+                // Darken scrim as you pull down the shade when unlocked
+                float behindFraction = getInterpolatedFraction();
+                behindFraction = (float) Math.pow(behindFraction, 0.8f);
+                mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+                mCurrentInFrontAlpha = 0;
+            } else if (mState == ScrimState.KEYGUARD) {
+                if (mUpdatePending) {
+                    return;
+                }
+
+                // Either darken of make the scrim transparent when you
+                // pull down the shade
+                float interpolatedFract = getInterpolatedFraction();
+                if (mDarkenWhileDragging) {
+                    mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+                            mScrimBehindAlphaKeyguard, interpolatedFract);
+                    mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+                } else {
+                    mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+                            interpolatedFract);
+                    mCurrentInFrontAlpha = 0;
+                }
+            } else {
+                Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+                return;
+            }
+
             if (mPinnedHeadsUpCount != 0) {
                 updateHeadsUpScrim(false);
             }
-            if (mKeyguardFadeoutAnimation != null && mTracking) {
-                mKeyguardFadeoutAnimation.cancel();
-            }
+
+            updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+            updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
         }
     }
 
-    public void setBouncerShowing(boolean showing) {
-        mBouncerShowing = showing;
-        mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
-        scheduleUpdate();
-    }
-
-    /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
-    public void prepareWakeUpFromAod() {
-        if (mWakingUpFromAodInProgress) {
-            return;
-        }
-        mWakingUpFromAodInProgress = true;
-        mWakingUpFromAodStarting = true;
-        mAnimateChange = false;
-        scheduleUpdate();
-        onPreDraw();
-    }
-
-    /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
-    public void wakeUpFromAod() {
-        if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
-            // Wake and unlocking has a separate transition that must not be interfered with.
-            mWakingUpFromAodStarting = false;
-            mWakingUpFromAodInProgress = false;
-            return;
-        }
-        if (mWakingUpFromAodStarting) {
-            mWakingUpFromAodInProgress = true;
-            mWakingUpFromAodStarting = false;
-            mAnimateChange = true;
-            scheduleUpdate();
-        }
-    }
-
-    public void setWakeAndUnlocking() {
-        mWakeAndUnlocking = true;
-        mAnimatingDozeUnlock = true;
-        mWakingUpFromAodStarting = false;
-        mWakingUpFromAodInProgress = false;
-        scheduleUpdate();
-    }
-
-    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
-            boolean skipFirstFrame) {
-        mWakeAndUnlocking = false;
-        mAnimateKeyguardFadingOut = true;
-        mDurationOverride = duration;
-        mAnimationDelay = delay;
-        mAnimateChange = true;
-        mSkipFirstFrame = skipFirstFrame;
-        mOnAnimationFinished = onAnimationFinished;
-
-        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
-            scheduleUpdate();
-
-            // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
-            // the changes we just scheduled.
-            onPreDraw();
-        } else {
-
-            // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
-            // with too many things in this case, in order to not skip the initial frames.
-            mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
-        }
-    }
-
-    public void abortKeyguardFadingOut() {
-        if (mAnimateKeyguardFadingOut) {
-            endAnimateKeyguardFadingOut(true /* force */);
-        }
-    }
-
-    public void animateKeyguardUnoccluding(long duration) {
-        mAnimateChange = false;
-        setScrimBehindAlpha(0f);
-        mAnimateChange = true;
-        scheduleUpdate();
-        mDurationOverride = duration;
-    }
-
-    public void animateGoingToFullShade(long delay, long duration) {
-        mDurationOverride = duration;
-        mAnimationDelay = delay;
-        mAnimateChange = true;
-        scheduleUpdate();
-    }
-
-    public void setDozing(boolean dozing) {
-        if (mDozing != dozing) {
-            mDozing = dozing;
-            scheduleUpdate();
-        }
-    }
-
-    public void setDozeInFrontAlpha(float alpha) {
-        mDozeInFrontAlpha = alpha;
-        updateScrimColor(mScrimInFront);
-    }
-
-    public void setDozeBehindAlpha(float alpha) {
-        mDozeBehindAlpha = alpha;
-        updateScrimColor(mScrimBehind);
-    }
-
-    public float getDozeBehindAlpha() {
-        return mDozeBehindAlpha;
-    }
-
-    public float getDozeInFrontAlpha() {
-        return mDozeInFrontAlpha;
-    }
-
+    /**
+     * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+     * @param notificationCount Number of visible notifications.
+     */
     public void setNotificationCount(int notificationCount) {
         final float maxNotificationDensity = 3;
         float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@
                 notificationDensity);
         if (mScrimBehindAlphaKeyguard != newAlpha) {
             mScrimBehindAlphaKeyguard = newAlpha;
-            mAnimateChange = true;
-            scheduleUpdate();
-        }
-    }
 
-    private float getScrimInFrontAlpha() {
-        return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
-                ? SCRIM_IN_FRONT_ALPHA_LOCKED
-                : SCRIM_IN_FRONT_ALPHA;
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+                scheduleUpdate();
+            }
+        }
     }
 
     /**
@@ -352,7 +364,7 @@
         if (mNeedsDrawableColorUpdate) {
             mNeedsDrawableColorUpdate = false;
             final GradientColors currentScrimColors;
-            if (mKeyguardShowing) {
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
                 // Always animate color changes if we're seeing the keyguard
                 mScrimInFront.setColors(mLockColors, true /* animated */);
                 mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
-        if (mAnimateKeyguardFadingOut || mForceHideScrims) {
-            setScrimInFrontAlpha(0f);
-            setScrimBehindAlpha(0f);
-        } else if (mWakeAndUnlocking) {
-            // During wake and unlock, we first hide everything behind a black scrim, which then
-            // gets faded out from animateKeyguardFadingOut. This must never be animated.
-            mAnimateChange = false;
-            if (mDozing) {
-                setScrimInFrontAlpha(0f);
-                setScrimBehindAlpha(1f);
-            } else {
-                setScrimInFrontAlpha(1f);
-                setScrimBehindAlpha(0f);
-            }
-        } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
-            updateScrimNormal();
-            setScrimInFrontAlpha(0);
-        } else {
-            updateScrimKeyguard();
-        }
-        mAnimateChange = false;
+        setScrimInFrontAlpha(mCurrentInFrontAlpha);
+        setScrimBehindAlpha(mCurrentBehindAlpha);
+
         dispatchScrimsVisible();
     }
 
     private void dispatchScrimsVisible() {
         boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
 
-        if (mScrimsVisble != scrimsVisible) {
-            mScrimsVisble = scrimsVisible;
+        if (mScrimsVisible != scrimsVisible) {
+            mScrimsVisible = scrimsVisible;
 
             mScrimVisibleListener.accept(scrimsVisible);
         }
     }
 
-    private void updateScrimKeyguard() {
-        if (mTracking && mDarkenWhileDragging) {
-            float behindFraction = Math.max(0, Math.min(mFraction, 1));
-            float fraction = 1 - behindFraction;
-            fraction = (float) Math.pow(fraction, 0.8f);
-            behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
-            setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
-        } else if (mBouncerShowing && !mBouncerIsKeyguard) {
-            setScrimInFrontAlpha(getScrimInFrontAlpha());
-            updateScrimNormal();
-        } else if (mBouncerShowing) {
-            setScrimInFrontAlpha(0f);
-            setScrimBehindAlpha(mScrimBehindAlpha);
-        } else {
-            float fraction = Math.max(0, Math.min(mFraction, 1));
-            if (mWakingUpFromAodStarting) {
-                setScrimInFrontAlpha(1f);
-            } else {
-                setScrimInFrontAlpha(0f);
-            }
-            setScrimBehindAlpha(fraction
-                    * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
-                    + mScrimBehindAlphaUnlocking);
-        }
-    }
-
-    private void updateScrimNormal() {
+    private float getInterpolatedFraction() {
         float frac = mFraction;
         // let's start this 20% of the way down the screen
         frac = frac * 1.2f - 0.2f;
         if (frac <= 0) {
-            setScrimBehindAlpha(0);
+            return 0;
         } else {
             // woo, special effects
-            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
-            setScrimBehindAlpha(k * mScrimBehindAlpha);
+            return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
         }
     }
 
@@ -455,102 +421,76 @@
 
     private void setScrimInFrontAlpha(float alpha) {
         setScrimAlpha(mScrimInFront, alpha);
-        if (alpha == 0f) {
-            mScrimInFront.setClickable(false);
-        } else {
-            // Eat touch events (unless dozing).
-            mScrimInFront.setClickable(!mDozing);
-        }
     }
 
     private void setScrimAlpha(View scrim, float alpha) {
-        updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
-    }
-
-    protected float getDozeAlpha(View scrim) {
-        return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
-    }
-
-    protected float getCurrentScrimAlpha(View scrim) {
-        return scrim == mScrimBehind ? mCurrentBehindAlpha
-                : scrim == mScrimInFront ? mCurrentInFrontAlpha
-                : mCurrentHeadsUpAlpha;
-    }
-
-    private void setCurrentScrimAlpha(View scrim, float alpha) {
-        if (scrim == mScrimBehind) {
-            mCurrentBehindAlpha = alpha;
-            mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
-        } else if (scrim == mScrimInFront) {
-            mCurrentInFrontAlpha = alpha;
+        if (alpha == 0f) {
+            scrim.setClickable(false);
         } else {
-            alpha = Math.max(0.0f, Math.min(1.0f, alpha));
-            mCurrentHeadsUpAlpha = alpha;
+            // Eat touch events (unless dozing).
+            scrim.setClickable(!(mState == ScrimState.AOD));
         }
+        updateScrim(mAnimateChange, scrim, alpha);
     }
 
-    private void updateScrimColor(View scrim) {
-        float alpha1 = getCurrentScrimAlpha(scrim);
+    private void updateScrimColor(View scrim, float alpha, int tint) {
+        alpha = Math.max(0, Math.min(1.0f, alpha));
         if (scrim instanceof ScrimView) {
             ScrimView scrimView = (ScrimView) scrim;
-            float dozeAlpha = getDozeAlpha(scrim);
-            float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
-            alpha = Math.max(0, Math.min(1.0f, alpha));
-            scrimView.setViewAlpha(alpha);
 
             Trace.traceCounter(Trace.TRACE_TAG_APP,
                     scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
                     (int) (alpha * 255));
 
-            int dozeTint = Color.TRANSPARENT;
-
-            boolean dozing = mAnimatingDozeUnlock || mDozing;
-            boolean frontScrimDozing = mWakingUpFromAodInProgress;
-            if (dozing || frontScrimDozing && scrim == mScrimInFront) {
-                dozeTint = Color.BLACK;
-            }
             Trace.traceCounter(Trace.TRACE_TAG_APP,
                     scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
-                    dozeTint == Color.BLACK ? 1 : 0);
+                    Color.alpha(tint));
 
-            scrimView.setTint(dozeTint);
+            scrimView.setTint(tint);
+            scrimView.setViewAlpha(alpha);
         } else {
-            scrim.setAlpha(alpha1);
+            scrim.setAlpha(alpha);
         }
         dispatchScrimsVisible();
     }
 
-    private void startScrimAnimation(final View scrim, float target) {
-        float current = getCurrentScrimAlpha(scrim);
-        ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+    private int getCurrentScrimTint(View scrim) {
+        return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+    }
+
+    private void startScrimAnimation(final View scrim, float current, float target) {
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+                Color.TRANSPARENT;
         anim.addUpdateListener(animation -> {
-            float alpha = (float) animation.getAnimatedValue();
-            setCurrentScrimAlpha(scrim, alpha);
-            updateScrimColor(scrim);
+            final float animAmount = (float) animation.getAnimatedValue();
+            final int finalScrimTint = scrim == mScrimInFront ?
+                    mCurrentInFrontTint : mCurrentBehindTint;
+            float alpha = MathUtils.lerp(current, target, animAmount);
+            int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+            updateScrimColor(scrim, alpha, tint);
             dispatchScrimsVisible();
         });
         anim.setInterpolator(getInterpolator());
         anim.setStartDelay(mAnimationDelay);
-        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+        anim.setDuration(mAnimationDuration);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (mKeyguardFadingOutInProgress) {
+                    mKeyguardFadeoutAnimation = null;
+                    mKeyguardFadingOutInProgress = false;
+                }
+                onFinished();
+
+                scrim.setTag(TAG_KEY_ANIM, null);
+                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
+                dispatchScrimsVisible();
+
                 if (!mDeferFinishedListener && mOnAnimationFinished != null) {
                     mOnAnimationFinished.run();
                     mOnAnimationFinished = null;
                 }
-                if (mKeyguardFadingOutInProgress) {
-                    mKeyguardFadeoutAnimation = null;
-                    mKeyguardFadingOutInProgress = false;
-                    mAnimatingDozeUnlock = false;
-                }
-                if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
-                    mWakingUpFromAodAnimationRunning = false;
-                    mWakingUpFromAodInProgress = false;
-                }
-                scrim.setTag(TAG_KEY_ANIM, null);
-                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
-                dispatchScrimsVisible();
             }
         });
         anim.start();
@@ -558,12 +498,6 @@
             mKeyguardFadingOutInProgress = true;
             mKeyguardFadeoutAnimation = anim;
         }
-        if (mWakingUpFromAodInProgress) {
-            mWakingUpFromAodAnimationRunning = true;
-        }
-        if (mSkipFirstFrame) {
-            anim.setCurrentPlayTime(16);
-        }
         scrim.setTag(TAG_KEY_ANIM, anim);
         scrim.setTag(TAG_KEY_ANIM_TARGET, target);
     }
@@ -582,19 +516,33 @@
     public boolean onPreDraw() {
         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
         mUpdatePending = false;
-        if (mDontAnimateBouncerChanges) {
-            mDontAnimateBouncerChanges = false;
+        if (mCallback != null) {
+            mCallback.onStart();
         }
         updateScrims();
-        mDurationOverride = -1;
-        mAnimationDelay = 0;
-        mSkipFirstFrame = false;
 
         // Make sure that we always call the listener even if we didn't start an animation.
         endAnimateKeyguardFadingOut(false /* force */);
         return true;
     }
 
+    private void onFinished() {
+        if (mWakeLockHeld) {
+            mWakeLock.release();
+            mWakeLockHeld = false;
+        }
+        if (mCallback != null) {
+            mCallback.onFinished();
+            mCallback = null;
+        }
+        // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+        // At the end of the animation we need to remove the tint.
+        if (mState == ScrimState.UNLOCKED) {
+            mCurrentInFrontTint = Color.TRANSPARENT;
+            mCurrentBehindTint = Color.TRANSPARENT;
+        }
+    }
+
     private void endAnimateKeyguardFadingOut(boolean force) {
         mAnimateKeyguardFadingOut = false;
         if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@
                 mOnAnimationFinished = null;
             }
             mKeyguardFadingOutInProgress = false;
-            if (!mWakeAndUnlocking || force)
-                mAnimatingDozeUnlock = false;
         }
     }
 
@@ -641,16 +587,19 @@
     }
 
     private void updateHeadsUpScrim(boolean animate) {
-        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
     }
 
-    private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
-        if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
-            return;
-        }
+    @VisibleForTesting
+    void setOnAnimationFinished(Runnable onAnimationFinished) {
+        mOnAnimationFinished = onAnimationFinished;
+    }
 
-        ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
-                TAG_KEY_ANIM);
+    private void updateScrim(boolean animate, View scrim, float alpha) {
+        final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+            : scrim.getAlpha();
+
+        ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
         float animEndValue = -1;
         if (previousAnimator != null) {
             if (animate || alpha == currentAlpha) {
@@ -664,9 +613,32 @@
                 animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
             }
         }
-        if (alpha != currentAlpha && alpha != animEndValue) {
+
+        final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+        if (mBlankScreen || blankingInProgress) {
+            if (!blankingInProgress) {
+                blankDisplay();
+            }
+            return;
+        } else if (!mScreenBlankingCallbackCalled) {
+            // Not blanking the screen. Letting the callback know that we're ready
+            // to replace what was on the screen before.
+            if (mCallback != null) {
+                mCallback.onDisplayBlanked();
+                mScreenBlankingCallbackCalled = true;
+            }
+        }
+
+        final ScrimView scrimView = scrim instanceof  ScrimView ? (ScrimView) scrim : null;
+        final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+        final boolean wantsTintUpdate = scrimView != null
+                && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+        if (wantsAlphaUpdate || wantsTintUpdate) {
             if (animate) {
-                startScrimAnimation(scrim, alpha);
+                final float fromAlpha = scrimView == null ? scrim.getAlpha()
+                        : scrimView.getViewAlpha();
+                startScrimAnimation(scrim, fromAlpha, alpha);
                 scrim.setTag(TAG_START_ALPHA, currentAlpha);
                 scrim.setTag(TAG_END_ALPHA, alpha);
             } else {
@@ -685,13 +657,62 @@
                     previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                 } else {
                     // update the alpha directly
-                    setCurrentScrimAlpha(scrim, alpha);
-                    updateScrimColor(scrim);
+                    updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+                    onFinished();
                 }
             }
+        } else {
+            onFinished();
         }
     }
 
+    private void blankDisplay() {
+        final float initialAlpha = mScrimInFront.getViewAlpha();
+        final int initialTint = mScrimInFront.getTint();
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        anim.addUpdateListener(animation -> {
+            final float amount = (float) animation.getAnimatedValue();
+            float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+            int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+            updateScrimColor(mScrimInFront, animAlpha, animTint);
+            dispatchScrimsVisible();
+        });
+        anim.setInterpolator(getInterpolator());
+        anim.setDuration(mDozeParameters.getPulseInDuration());
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCallback != null) {
+                    mCallback.onDisplayBlanked();
+                    mScreenBlankingCallbackCalled = true;
+                }
+                Runnable blankingCallback = () -> {
+                    mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+                    mBlankScreen = false;
+                    // Try again.
+                    updateScrims();
+                };
+
+                // Setting power states can happen after we push out the frame. Make sure we
+                // stay fully opaque until the power state request reaches the lower levels.
+                getHandler().postDelayed(blankingCallback, 100);
+
+            }
+        });
+        anim.start();
+        mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+        // Finish animation if we're already at its final state
+        if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+            anim.end();
+        }
+    }
+
+    @VisibleForTesting
+    protected Handler getHandler() {
+        return Handler.getMain();
+    }
+
     /**
      * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
      * the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +740,13 @@
         return alpha * expandFactor;
     }
 
-    public void forceHideScrims(boolean hide, boolean animated) {
-        mForceHideScrims = hide;
-        mAnimateChange = animated;
-        scheduleUpdate();
-    }
-
-    public void dontAnimateBouncerChangesUntilNextFrame() {
-        mDontAnimateBouncerChanges = true;
-    }
-
     public void setExcludedBackgroundArea(Rect area) {
         mScrimBehind.setExcludedArea(area);
     }
 
     public int getBackgroundColor() {
         int color = mLockColors.getMainColor();
-        return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+        return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
                 Color.red(color), Color.green(color), Color.blue(color));
     }
 
@@ -764,27 +775,41 @@
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
             mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_DARK, mKeyguardShowing);
+                    ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
     }
 
-    public void dump(PrintWriter pw) {
-        pw.println(" ScrimController:");
+    @VisibleForTesting
+    protected WakeLock createWakeLock() {
+         return new DelayedWakeLock(getHandler(),
+                WakeLock.createPartial(mContext, "Doze"));
+    }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(" ScrimController:");
+        pw.print(" state:"); pw.println(mState);
         pw.print("   frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
         pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
-        pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
 
         pw.print("   backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
         pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
-        pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
 
-        pw.print("   mBouncerShowing="); pw.println(mBouncerShowing);
         pw.print("   mTracking="); pw.println(mTracking);
-        pw.print("   mForceHideScrims="); pw.println(mForceHideScrims);
+    }
+
+    public interface Callback {
+        default void onStart() {
+        }
+        default void onDisplayBlanked() {
+        }
+        default void onFinished() {
+        }
+        default void onCancelled() {
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 0000000..0db98f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+    /**
+     * Initial state.
+     */
+    UNINITIALIZED,
+
+    /**
+     * On the lock screen.
+     */
+    KEYGUARD {
+
+        @Override
+        public void prepare(ScrimState previousState) {
+            // DisplayPowerManager will blank the screen, we'll just
+            // set our scrim to black in this frame to avoid flickering and
+            // fade it out afterwards.
+            mBlankScreen = previousState == ScrimState.AOD;
+            if (previousState == ScrimState.AOD) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
+            mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+            mCurrentInFrontAlpha = 0;
+        }
+    },
+
+    /**
+     * Showing password challenge.
+     */
+    BOUNCER {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+            mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+        }
+    },
+
+    /**
+     * Changing screen brightness from quick settings.
+     */
+    BRIGHTNESS_MIRROR {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 0;
+            mCurrentInFrontAlpha = 0;
+        }
+    },
+
+    /**
+     * Always on display or screen off.
+     */
+    AOD {
+        @Override
+        public void prepare(ScrimState previousState) {
+            if (previousState == ScrimState.PULSING) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
+            final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+            mBlankScreen = previousState == ScrimState.PULSING;
+            mCurrentBehindAlpha = 1;
+            mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+            mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindTint = Color.BLACK;
+            // DisplayPowerManager will blank the screen for us, we just need
+            // to set our state.
+            mAnimateChange = false;
+        }
+    },
+
+    /**
+     * When phone wakes up because you received a notification.
+     */
+    PULSING {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 1;
+            mCurrentInFrontAlpha = 0;
+            mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindTint = Color.BLACK;
+            mBlankScreen = true;
+            updateScrimColor(mScrimInFront, 1, Color.BLACK);
+        }
+    },
+
+    /**
+     * Unlocked on top of an app (launcher or any other activity.)
+     */
+    UNLOCKED {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 0;
+            mCurrentInFrontAlpha = 0;
+            mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+            if (previousState == ScrimState.AOD) {
+                // Fade from black to transparent when coming directly from AOD
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1, Color.BLACK);
+                // Scrims should still be black at the end of the transition.
+                mCurrentInFrontTint = Color.BLACK;
+                mCurrentBehindTint = Color.BLACK;
+                mBlankScreen = true;
+            } else {
+                // Scrims should still be black at the end of the transition.
+                mCurrentInFrontTint = Color.TRANSPARENT;
+                mCurrentBehindTint = Color.TRANSPARENT;
+                mBlankScreen = false;
+            }
+        }
+    };
+
+    boolean mBlankScreen = false;
+    long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+    int mCurrentInFrontTint = Color.TRANSPARENT;
+    int mCurrentBehindTint = Color.TRANSPARENT;
+    boolean mAnimateChange = true;
+    float mCurrentInFrontAlpha;
+    float mCurrentBehindAlpha;
+    float mAodFrontScrimAlpha;
+    float mScrimBehindAlphaKeyguard;
+    ScrimView mScrimInFront;
+    ScrimView mScrimBehind;
+    DozeParameters mDozeParameters;
+
+    public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+        mScrimInFront = scrimInFront;
+        mScrimBehind = scrimBehind;
+        mDozeParameters = dozeParameters;
+    }
+
+    public void prepare(ScrimState previousState) {
+    }
+
+    public float getFrontAlpha() {
+        return mCurrentInFrontAlpha;
+    }
+
+    public float getBehindAlpha() {
+        return mCurrentBehindAlpha;
+    }
+
+    public int getFrontTint() {
+        return mCurrentInFrontTint;
+    }
+
+    public int getBehindTint() {
+        return mCurrentBehindTint;
+    }
+
+    public long getAnimationDuration() {
+        return mAnimationDuration;
+    }
+
+    public boolean getBlanksScreen() {
+        return mBlankScreen;
+    }
+
+    public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+        Trace.traceCounter(Trace.TRACE_TAG_APP,
+                scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+                (int) (alpha * 255));
+
+        Trace.traceCounter(Trace.TRACE_TAG_APP,
+                scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+                Color.alpha(tint));
+
+        scrim.setTint(tint);
+        scrim.setViewAlpha(alpha);
+    }
+
+    public boolean getAnimateChange() {
+        return mAnimateChange;
+    }
+
+    public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+        mAodFrontScrimAlpha = aodFrontScrimAlpha;
+    }
+
+    public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+        mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 43cc0f7..dc8100f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -238,6 +238,7 @@
         .OnChildLocationsChangedListener;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -386,6 +387,7 @@
 
     private VolumeComponent mVolumeComponent;
     private BrightnessMirrorController mBrightnessMirrorController;
+    private boolean mBrightnessMirrorVisible;
     protected FingerprintUnlockController mFingerprintUnlockController;
     private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
@@ -646,6 +648,31 @@
         }
     };
 
+    // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+    // this animation is tied to the scrim for historic reasons.
+    // TODO: notify when keyguard has faded away instead of the scrim.
+    private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+            .Callback() {
+        @Override
+        public void onFinished() {
+            notifyKeyguardState();
+        }
+
+        @Override
+        public void onCancelled() {
+            notifyKeyguardState();
+        }
+
+        private void notifyKeyguardState() {
+            if (mStatusBarKeyguardViewManager == null) {
+                Log.w(TAG, "Tried to notify keyguard visibility when "
+                        + "mStatusBarKeyguardViewManager was null");
+                return;
+            }
+            mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+        }
+    };
+
     private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
@@ -1045,7 +1072,7 @@
                     if (mStatusBarWindowManager != null) {
                         mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
                     }
-                });
+                }, new DozeParameters(mContext));
         if (mScrimSrcModeEnabled) {
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1081,7 +1108,10 @@
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mIconController);
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
-                    mScrimController);
+                    (visible) -> {
+                        mBrightnessMirrorVisible = visible;
+                        updateScrimController();
+                    });
             fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
                 QS qs = (QS) f;
                 if (qs instanceof QSFragment) {
@@ -1457,8 +1487,7 @@
                 mDozeScrimController, keyguardViewMediator,
                 mScrimController, this, UnlockMethodCache.getInstance(mContext));
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
-                getBouncerContainer(), mScrimController,
-                mFingerprintUnlockController);
+                getBouncerContainer(), mFingerprintUnlockController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -2640,7 +2669,7 @@
     }
 
     public boolean isPulsing() {
-        return mDozeScrimController.isPulsing();
+        return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
     @Override
@@ -3348,7 +3377,7 @@
         }
 
         if (mScrimController != null) {
-            mScrimController.dump(pw);
+            mScrimController.dump(fd, pw, args);
         }
 
         if (DUMPTRUCK) {
@@ -3413,7 +3442,19 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
-        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+        mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
+          public void setRemoteInputActive(NotificationData.Entry entry,
+                  boolean remoteInputActive) {
+              mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+          }
+          public void lockScrollTo(NotificationData.Entry entry) {
+              mStackScroller.lockScrollTo(entry.row);
+          }
+          public void requestDisallowLongPressAndDismiss() {
+              mStackScroller.requestDisallowLongPress();
+              mStackScroller.requestDisallowDismiss();
+          }
+        });
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -4081,7 +4122,6 @@
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mLaunchTransitionFadingAway = false;
-        mScrimController.forceHideScrims(false /* hide */, false /* animated */);
         updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
@@ -4114,7 +4154,7 @@
             if (beforeFading != null) {
                 beforeFading.run();
             }
-            mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+            updateScrimController();
             updateMediaMetaData(false, true);
             mNotificationPanel.setAlpha(1);
             mStackScroller.setParentNotFullyVisible(true);
@@ -4145,6 +4185,13 @@
                 .setStartDelay(0)
                 .setDuration(FADE_KEYGUARD_DURATION_PULSING)
                 .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        hideKeyguard();
+                        mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                    }
+                })
                 .start();
     }
 
@@ -4152,7 +4199,6 @@
      * Plays the animation when an activity that was occluding Keyguard goes away.
      */
     public void animateKeyguardUnoccluding() {
-        mScrimController.animateKeyguardUnoccluding(500);
         mNotificationPanel.setExpandedFraction(0f);
         animateExpandNotificationsPanel();
     }
@@ -4340,11 +4386,6 @@
                 mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
             }
         }
-        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            mScrimController.setKeyguardShowing(true);
-        } else {
-            mScrimController.setKeyguardShowing(false);
-        }
         mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
         updateTheme();
         updateDozingState();
@@ -4352,6 +4393,7 @@
         updateStackScrollerState(goingToFullShade, fromShadeLocked);
         updateNotifications();
         checkBarModes();
+        updateScrimController();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
         mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                 mUnlockMethodCache.isMethodSecure(),
@@ -4415,11 +4457,10 @@
         boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
         mNotificationPanel.setDozing(mDozing, animate);
         mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
-        mScrimController.setDozing(mDozing);
+        mDozeScrimController.setDozing(mDozing);
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        mDozeScrimController.setDozing(mDozing, animate);
         updateRowStates();
         Trace.endSection();
     }
@@ -4914,6 +4955,7 @@
         if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
         updateHideIconsForBouncer(true /* animate */);
         recomputeDisableFlags(true /* animate */);
+        updateScrimController();
     }
 
     public void cancelCurrentTouch() {
@@ -4965,12 +5007,10 @@
             mStackScroller.setAnimationsEnabled(true);
             mVisualStabilityManager.setScreenOn(true);
             mNotificationPanel.setTouchDisabled(false);
-
-            maybePrepareWakeUpFromAod();
-
             mDozeServiceHost.stopDozing();
             updateVisibleToUser();
             updateIsKeyguard();
+            updateScrimController();
         }
     };
 
@@ -4980,18 +5020,16 @@
             mFalsingManager.onScreenTurningOn();
             mNotificationPanel.onScreenTurningOn();
 
-            maybePrepareWakeUpFromAod();
-
             if (mLaunchCameraOnScreenTurningOn) {
                 mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
                 mLaunchCameraOnScreenTurningOn = false;
             }
+
+            updateScrimController();
         }
 
         @Override
         public void onScreenTurnedOn() {
-            mScrimController.wakeUpFromAod();
-            mDozeScrimController.onScreenTurnedOn();
         }
 
         @Override
@@ -5009,13 +5047,6 @@
         return mWakefulnessLifecycle.getWakefulness();
     }
 
-    private void maybePrepareWakeUpFromAod() {
-        int wakefulness = mWakefulnessLifecycle.getWakefulness();
-        if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
-            mScrimController.prepareWakeUpFromAod();
-        }
-    }
-
     private void vibrateForCameraGesture() {
         // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
         mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5098,12 +5129,12 @@
             if (!mDeviceInteractive) {
                 // Avoid flickering of the scrim when we instant launch the camera and the bouncer
                 // comes on.
-                mScrimController.dontAnimateBouncerChangesUntilNextFrame();
                 mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
             }
             if (isScreenTurningOnOrOn()) {
                 if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
                 mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+                updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
                 // we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5145,15 +5176,16 @@
     private void updateDozing() {
         Trace.beginSection("StatusBar#updateDozing");
         // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
-        mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+        boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
                 || mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
         // When in wake-and-unlock we may not have received a change to mState
         // but we still should not be dozing, manually set to false.
         if (mFingerprintUnlockController.getMode() ==
                 FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
-            mDozing = false;
+            dozing = false;
         }
+        mDozing = dozing;
         mStatusBarWindowManager.setDozing(mDozing);
         mStatusBarKeyguardViewManager.setDozing(mDozing);
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5163,6 +5195,24 @@
         Trace.endSection();
     }
 
+    public void updateScrimController() {
+        if (mBouncerShowing) {
+            mScrimController.transitionTo(ScrimState.BOUNCER);
+        } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+        } else if (mBrightnessMirrorVisible) {
+            mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+        } else if (isPulsing()) {
+            // Handled in DozeScrimController#setPulsing
+        } else if (mDozing) {
+            mScrimController.transitionTo(ScrimState.AOD);
+        } else if (mIsKeyguard) {
+            mScrimController.transitionTo(ScrimState.KEYGUARD);
+        } else {
+            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+        }
+    }
+
     public boolean isKeyguardShowing() {
         if (mStatusBarKeyguardViewManager == null) {
             Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5222,7 +5272,6 @@
             }
 
             mDozeScrimController.pulse(new PulseCallback() {
-
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
@@ -5307,11 +5356,6 @@
         }
 
         @Override
-        public void abortPulsing() {
-            mDozeScrimController.abortPulsing();
-        }
-
-        @Override
         public void extendPulse() {
             mDozeScrimController.extendPulse();
         }
@@ -5347,7 +5391,7 @@
 
         @Override
         public void setAodDimmingScrim(float scrimOpacity) {
-            mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+            ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
         }
 
         public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bcde556..ef05bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -71,17 +70,14 @@
 
     protected final Context mContext;
     private final StatusBarWindowManager mStatusBarWindowManager;
-    private final boolean mDisplayBlanksAfterDoze;
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
     protected StatusBar mStatusBar;
-    private ScrimController mScrimController;
     private FingerprintUnlockController mFingerprintUnlockController;
 
     private ViewGroup mContainer;
 
-    private boolean mScreenTurnedOn;
     protected KeyguardBouncer mBouncer;
     protected boolean mShowing;
     protected boolean mOccluded;
@@ -95,12 +91,10 @@
     private boolean mLastBouncerDismissible;
     protected boolean mLastRemoteInputActive;
     private boolean mLastDozing;
-    private boolean mLastDeferScrimFadeOut;
     private int mLastFpMode;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
-    private boolean mDeferScrimFadeOut;
 
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
     private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
-        mDisplayBlanksAfterDoze = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_displayBlanksAfterDoze);
     }
 
     public void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
-            ScrimController scrimController,
             FingerprintUnlockController fingerprintUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry) {
         mStatusBar = statusBar;
         mContainer = container;
-        mScrimController = scrimController;
         mFingerprintUnlockController = fingerprintUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@
     public void show(Bundle options) {
         mShowing = true;
         mStatusBarWindowManager.setKeyguardShowing(true);
-        mScrimController.abortKeyguardFadingOut();
         reset(true /* hideBouncerWhenShowing */);
     }
 
@@ -253,15 +242,7 @@
     }
 
     public void onScreenTurnedOn() {
-        Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
-        mScreenTurnedOn = true;
-        if (mDeferScrimFadeOut) {
-            mDeferScrimFadeOut = false;
-            animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
-                    true /* skipFirstFrame */);
-            updateStates();
-        }
-        Trace.endSection();
+        // TODO: remove
     }
 
     @Override
@@ -285,7 +266,7 @@
     }
 
     public void onScreenTurnedOff() {
-        mScreenTurnedOn = false;
+        // TODO: remove
     }
 
     public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
                     hideBouncer(true /* destroyView */);
                     updateStates();
-                    mScrimController.animateKeyguardFadingOut(
-                            StatusBar.FADE_KEYGUARD_START_DELAY,
-                            StatusBar.FADE_KEYGUARD_DURATION, null,
-                            false /* skipFirstFrame */);
                 }
             }, new Runnable() {
                 @Override
@@ -400,36 +377,16 @@
             mFingerprintUnlockController.startKeyguardFadingAway();
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
-                mStatusBarWindowManager.setKeyguardFadingAway(true);
                 mStatusBar.fadeKeyguardWhilePulsing();
-                animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                        mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+                wakeAndUnlockDejank();
             } else {
                 mFingerprintUnlockController.startKeyguardFadingAway();
                 mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
                 boolean staying = mStatusBar.hideKeyguard();
                 if (!staying) {
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
-                    if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
-                        boolean turnedOnSinceAuth =
-                                mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
-                        if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
-                            // Not ready to animate yet; either because the screen is not on yet,
-                            // or it is on but will turn off before waking out of doze.
-                            mDeferScrimFadeOut = true;
-                        } else {
-
-                            // Screen is already on, don't defer with fading out.
-                            animateScrimControllerKeyguardFadingOut(0,
-                                    WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
-                                    true /* skipFirstFrame */);
-                        }
-                    } else {
-                        animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                                false /* skipFirstFrame */);
-                    }
+                    wakeAndUnlockDejank();
                 } else {
-                    mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
                     mStatusBar.finishKeyguardFadingAway();
                     mFingerprintUnlockController.finishKeyguardFadingAway();
                 }
@@ -449,30 +406,17 @@
         mBouncer.prepare();
     }
 
-    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
-            boolean skipFirstFrame) {
-        animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
-                skipFirstFrame);
+    public void onKeyguardFadedAway() {
+        mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+                100);
+        mStatusBar.finishKeyguardFadingAway();
+        mFingerprintUnlockController.finishKeyguardFadingAway();
+        WindowManagerGlobal.getInstance().trimMemory(
+                ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
     }
 
-    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
-            final Runnable endRunnable, boolean skipFirstFrame) {
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
-        mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
-            @Override
-            public void run() {
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-                mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
-                        100);
-                mStatusBar.finishKeyguardFadingAway();
-                mFingerprintUnlockController.finishKeyguardFadingAway();
-                WindowManagerGlobal.getInstance().trimMemory(
-                        ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
-            }
-        }, skipFirstFrame);
+    private void wakeAndUnlockDejank() {
         if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
                 && LatencyTracker.isEnabled(mContext)) {
             DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@
         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
             mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
             mStatusBar.setBouncerShowing(bouncerShowing);
-            mScrimController.setBouncerShowing(bouncerShowing);
         }
 
         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@
         mLastBouncerDismissible = bouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
         mLastDozing = mDozing;
-        mLastDeferScrimFadeOut = mDeferScrimFadeOut;
         mLastFpMode = mFingerprintUnlockController.getMode();
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
@@ -624,7 +566,7 @@
         boolean keyguardShowing = mShowing && !mOccluded;
         boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
-                || mRemoteInputActive) && !mDeferScrimFadeOut;
+                || mRemoteInputActive);
     }
 
     /**
@@ -634,7 +576,7 @@
         boolean keyguardShowing = mLastShowing && !mLastOccluded;
         boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
-                || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+                || mLastRemoteInputActive);
     }
 
     public boolean shouldDismissOnMenuPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index b0553d7..a011952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
 import android.util.ArraySet;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
@@ -26,46 +27,47 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
+import java.util.function.Consumer;
+
 /**
  * Controls showing and hiding of the brightness mirror.
  */
 public class BrightnessMirrorController
         implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
 
-    private final NotificationStackScrollLayout mStackScroller;
-    public long TRANSITION_DURATION_OUT = 150;
-    public long TRANSITION_DURATION_IN = 200;
+    private final static long TRANSITION_DURATION_OUT = 150;
+    private final static long TRANSITION_DURATION_IN = 200;
 
     private final StatusBarWindowView mStatusBarWindow;
-    private final ScrimController mScrimController;
+    private final NotificationStackScrollLayout mStackScroller;
+    private final Consumer<Boolean> mVisibilityCallback;
     private final View mNotificationPanel;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
     private final int[] mInt2Cache = new int[2];
     private View mBrightnessMirror;
 
     public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
-            ScrimController scrimController) {
+            @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
         mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
-        mScrimController = scrimController;
+        mVisibilityCallback = visibilityCallback;
     }
 
     public void showMirror() {
         mBrightnessMirror.setVisibility(View.VISIBLE);
         mStackScroller.setFadingOut(true);
-        mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+        mVisibilityCallback.accept(true);
         outAnimation(mNotificationPanel.animate())
                 .withLayer();
     }
 
     public void hideMirror() {
-        mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+        mVisibilityCallback.accept(false);
         inAnimation(mNotificationPanel.animate())
                 .withLayer()
                 .withEndAction(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de4..4fc50442 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,11 +53,9 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
@@ -82,8 +80,6 @@
 
     private NotificationData.Entry mEntry;
 
-    private ScrollContainer mScrollContainer;
-    private View mScrollContainerChild;
     private boolean mRemoved;
 
     private int mRevealCx;
@@ -347,41 +343,16 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            findScrollContainer();
-            if (mScrollContainer != null) {
-                mScrollContainer.requestDisallowLongPress();
-                mScrollContainer.requestDisallowDismiss();
-            }
+            mController.requestDisallowLongPressAndDismiss();
         }
         return super.onInterceptTouchEvent(ev);
     }
 
     public boolean requestScrollTo() {
-        findScrollContainer();
-        mScrollContainer.lockScrollTo(mScrollContainerChild);
+        mController.lockScrollTo(mEntry);
         return true;
     }
 
-    private void findScrollContainer() {
-        if (mScrollContainer == null) {
-            mScrollContainerChild = null;
-            ViewParent p = this;
-            while (p != null) {
-                if (mScrollContainerChild == null && p instanceof ExpandableView) {
-                    mScrollContainerChild = (View) p;
-                }
-                if (p.getParent() instanceof ScrollContainer) {
-                    mScrollContainer = (ScrollContainer) p.getParent();
-                    if (mScrollContainerChild == null) {
-                        mScrollContainerChild = (View) p;
-                    }
-                    break;
-                }
-                p = p.getParent();
-            }
-        }
-    }
-
     public boolean isActive() {
         return mEditText.isFocused() && mEditText.isEnabled();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf..f5ae88b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@
                     avatar = new UserIconDrawable(avatarSize)
                             .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
                 } else {
-                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+                    avatar = UserIcons.getDefaultUserIcon(
+                            context.getResources(),
+                            isGuest? UserHandle.USER_NULL : userId,
                             lightIcon);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 0f498bc..7006d38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@
             if (item.isAddUser) {
                 return context.getDrawable(R.drawable.ic_add_circle_qs);
             }
-            Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+            Drawable icon = UserIcons.getDefaultUserIcon(
+                    context.getResources(), item.resolveId(), /* light= */ false);
             if (item.isGuest) {
                 icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
                         Mode.SRC_IN);
@@ -959,7 +960,7 @@
                 }
                 int id = user.id;
                 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
-                        id, /* light= */ false));
+                        mContext.getResources(), id, /* light= */ false));
                 mUserManager.setUserIcon(id, icon);
                 switchToUserId(id);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d..2f6e658 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@
      * @return an animation filter for this animation.
      */
     public AnimationFilter getAnimationFilter() {
-        return new AnimationFilter();
+        return new AnimationFilter() {
+            @Override
+            public boolean shouldAnimateProperty(Property property) {
+                return true;
+            }
+        };
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104..c0241e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -1260,4 +1260,17 @@
     public boolean isUserLocked() {
         return mUserLocked;
     }
+
+    public void setCurrentBottomRoundness(float currentBottomRoundness) {
+        boolean last = true;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            ExpandableNotificationRow child = mChildren.get(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+            child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+            last = false;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index efe049a..ebebfac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -108,8 +112,7 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
-        VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -121,12 +124,23 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
+    private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+            "sidePaddings",
+            NotificationStackScrollLayout::setCurrentSidePadding,
+            NotificationStackScrollLayout::getCurrentSidePadding,
+            R.id.side_padding_animator_tag,
+            R.id.side_padding_animator_end_tag,
+            R.id.side_padding_animator_start_tag);
+    private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private boolean mSwipingInProgress;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
+    private final Path mBackgroundPath = new Path();
+    private final float[] mBackgroundRadii = new float[8];
     private final boolean mShouldDrawNotificationBackground;
 
     private float mExpandedHeight;
@@ -157,6 +171,7 @@
     private int mTopPadding;
     private int mBottomMargin;
     private int mBottomInset = 0;
+    private float mCurrentSidePadding;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -383,6 +398,9 @@
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mAnimateScroll = this::animateScroll;
+    private int mCornerRadius;
+    private int mLockscreenSidePaddings;
+    private int mSidePaddings;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -419,6 +437,8 @@
                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
 
         updateWillNotDraw();
+        mBackgroundPaint.setAntiAlias(true);
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
         if (DEBUG) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
@@ -466,8 +486,7 @@
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
                 && mCurrentBounds.top < mCurrentBounds.bottom) {
-            canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
-                    mBackgroundPaint);
+            canvas.drawPath(mBackgroundPath, mBackgroundPaint);
         }
 
         if (DEBUG) {
@@ -520,8 +539,12 @@
                 R.dimen.min_top_overscroll_to_qs);
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
         mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+        mLockscreenSidePaddings = res.getDimensionPixelSize(
+                R.dimen.notification_lockscreen_side_paddings);
         mMinInteractionHeight = res.getDimensionPixelSize(
                 R.dimen.notification_min_interaction_height);
+        mCornerRadius = res.getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
     }
 
     public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -1195,7 +1218,6 @@
         mScrollingEnabled = enable;
     }
 
-    @Override
     public void lockScrollTo(View v) {
         if (mForcedScroll == v) {
             return;
@@ -1204,7 +1226,6 @@
         scrollTo(v);
     }
 
-    @Override
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
         int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -2221,9 +2242,31 @@
         mScrimController.setExcludedBackgroundArea(
                 mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
                         : mCurrentBounds);
+        updateBackgroundPath();
         invalidate();
     }
 
+    private void updateBackgroundPath() {
+        mBackgroundPath.reset();
+        float topRoundness = 0;
+        if (mFirstVisibleBackgroundChild != null) {
+            topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+        }
+        topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+        float bottomRoundNess = mCornerRadius;
+        mBackgroundRadii[0] = topRoundness;
+        mBackgroundRadii[1] = topRoundness;
+        mBackgroundRadii[2] = topRoundness;
+        mBackgroundRadii[3] = topRoundness;
+        mBackgroundRadii[4] = bottomRoundNess;
+        mBackgroundRadii[5] = bottomRoundNess;
+        mBackgroundRadii[6] = bottomRoundNess;
+        mBackgroundRadii[7] = bottomRoundNess;
+        mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+                getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+                Path.Direction.CCW);
+    }
+
     /**
      * Update the background bounds to the new desired bounds
      */
@@ -2236,6 +2279,8 @@
             mBackgroundBounds.left = mTempInt2[0];
             mBackgroundBounds.right = mTempInt2[0] + getWidth();
         }
+        mBackgroundBounds.left += mCurrentSidePadding;
+        mBackgroundBounds.right -= mCurrentSidePadding;
         if (!mIsExpanded) {
             mBackgroundBounds.top = 0;
             mBackgroundBounds.bottom = 0;
@@ -2820,16 +2865,45 @@
     private void updateFirstAndLastBackgroundViews() {
         ActivatableNotificationView firstChild = getFirstChildWithBackground();
         ActivatableNotificationView lastChild = getLastChildWithBackground();
+        boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+        boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
         if (mAnimationsEnabled && mIsExpanded) {
-            mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
-            mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+            mAnimateNextBackgroundTop = firstChanged;
+            mAnimateNextBackgroundBottom = lastChanged;
         } else {
             mAnimateNextBackgroundTop = false;
             mAnimateNextBackgroundBottom = false;
         }
+        if (firstChanged && mFirstVisibleBackgroundChild != null
+                && !mFirstVisibleBackgroundChild.isRemoved()) {
+            mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+                    mFirstVisibleBackgroundChild.isShown());
+        }
+        if (lastChanged && mLastVisibleBackgroundChild != null
+                && !mLastVisibleBackgroundChild.isRemoved()) {
+            mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+                    mLastVisibleBackgroundChild.isShown());
+        }
         mFirstVisibleBackgroundChild = firstChild;
         mLastVisibleBackgroundChild = lastChild;
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
+        applyRoundedNess();
+    }
+
+    private void applyRoundedNess() {
+        if (mFirstVisibleBackgroundChild != null) {
+            mFirstVisibleBackgroundChild.setTopRoundness(
+                    mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f,
+                    mFirstVisibleBackgroundChild.isShown()
+                            && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+        }
+        if (mLastVisibleBackgroundChild != null) {
+            mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+                    mLastVisibleBackgroundChild.isShown()
+                            && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+        }
+        updateBackgroundPath();
+        invalidate();
     }
 
     private void onViewAddedInternal(View child) {
@@ -2838,6 +2912,7 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
+        updateCurrentSidePaddings(child);
     }
 
     private void updateHideSensitiveForChild(View child) {
@@ -3321,12 +3396,10 @@
         }
     }
 
-    @Override
     public void requestDisallowLongPress() {
         cancelLongPress();
     }
 
-    @Override
     public void requestDisallowDismiss() {
         mDisallowDismissInThisMotion = true;
     }
@@ -4285,6 +4358,43 @@
     public void setStatusBarState(int statusBarState) {
         mStatusBarState = statusBarState;
         mAmbientState.setStatusBarState(statusBarState);
+        applyRoundedNess();
+        updateSidePaddings();
+    }
+
+    private void updateSidePaddings() {
+        int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+        if (sidePaddings != mSidePaddings) {
+            boolean animate = isShown();
+            mSidePaddings = sidePaddings;
+            PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+                    SIDE_PADDING_PROPERTIES, animate);
+        }
+    }
+
+    protected void setCurrentSidePadding(float sidePadding) {
+        mCurrentSidePadding = sidePadding;
+        updateBackground();
+        applySidePaddingsToChildren();
+    }
+
+    private void applySidePaddingsToChildren() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            updateCurrentSidePaddings(view);
+        }
+    }
+
+    private void updateCurrentSidePaddings(View view) {
+        if (!(view instanceof ExpandableOutlineView)) {
+            return;
+        }
+        ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+        outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+    }
+
+    protected float getCurrentSidePadding() {
+        return mCurrentSidePadding;
     }
 
     public void setExpandingVelocity(float expandingVelocity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
-    /**
-     * Request that the view does not perform long press for the current touch.
-     */
-    void requestDisallowLongPress();
-
-    /**
-     * Request that the view is made visible by scrolling to it.
-     * Return true if it scrolls.
-     */
-    boolean scrollTo(View v);
-
-    /**
-     * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
-     * scrolls, or {@param v} loses focus or is detached.
-     */
-    void lockScrollTo(View v);
-
-    /**
-     * Request that the view does not dismiss for the current touch.
-     */
-    void requestDisallowDismiss();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 27b730cd..682b849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
-import android.app.Notification;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -64,8 +63,8 @@
     private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
     private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
 
-    private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
-            = new PropertyAnimator.AnimatableProperty() {
+    private static final AnimatableProperty SCALE_X_PROPERTY
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
@@ -88,8 +87,8 @@
         }
     };
 
-    private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
-            = new PropertyAnimator.AnimatableProperty() {
+    private static final AnimatableProperty SCALE_Y_PROPERTY
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
@@ -251,7 +250,7 @@
         return getChildTag(view, tag) != null;
     }
 
-    public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+    public static boolean isAnimating(View view, AnimatableProperty property) {
         return getChildTag(view, property.getAnimatorTag()) != null;
     }
 
@@ -403,7 +402,7 @@
         startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
     }
 
-    private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+    private void updateAnimation(View view, AnimatableProperty property,
             float endValue) {
         PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0d41e20..383d327 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -441,7 +441,8 @@
                 .withEndAction(() -> mDialog.dismiss())
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .start();
-        if (mAccessibilityMgr.isEnabled()) {
+        if (mAccessibilityMgr.isObservedEventType(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
             event.setPackageName(mContext.getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index b0c9f328..1c104cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -19,12 +19,13 @@
 import android.annotation.NonNull;
 import android.app.PendingIntent;
 
+import com.android.systemui.util.wakelock.WakeLock;
+
 /**
  * A rudimentary fake for DozeHost.
  */
 class DozeHostFake implements DozeHost {
     Callback callback;
-    boolean pulseAborted;
     boolean pulseExtended;
     boolean animateWakeup;
     boolean dozing;
@@ -92,11 +93,6 @@
     }
 
     @Override
-    public void abortPulsing() {
-        pulseAborted = true;
-    }
-
-    @Override
     public void extendPulse() {
         pulseExtended = true;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 004ff29..1c9c794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.qs.QSTileHost;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
@@ -108,6 +109,7 @@
     }
 
     @Test
+    @Ignore("flaky")
     public void testStaleTimeout() throws InterruptedException {
         when(mTile.getStaleTimeout()).thenReturn(5l);
         clearInvocations(mTile);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index 6e59d10..2ff86c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -39,9 +39,8 @@
     private ExpandableNotificationRow mRow;
 
     @Before
-    @UiThreadTest
-    public void setUp() {
-        mRow = new ExpandableNotificationRow(mContext, null);
+    public void setUp() throws Exception {
+        mRow = new NotificationTestHelper(mContext).createRow();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
index 4c3bf10..42dad11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
@@ -86,9 +86,9 @@
 
     @Test
     public void testSetViewAlpha_propagatesToDrawable() {
-        float alpha = 0.5f;
+        final float alpha = 0.5f;
         mView.setViewAlpha(alpha);
-        assertEquals(mView.getViewAlpha(), alpha);
+        assertEquals("View alpha did not propagate to drawable", alpha, mView.getViewAlpha());
     }
 
     @Test
@@ -97,7 +97,7 @@
         Canvas canvas = mock(Canvas.class);
         mView.onDraw(canvas);
         // One time for each rect side
-        verify(canvas, times(4)).clipRect(anyInt(), anyInt(), anyInt(), anyInt());
+        verify(canvas, times(8)).clipRect(anyInt(), anyInt(), anyInt(), anyInt());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index eaa073c..a153140 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -35,7 +35,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.stack.AnimationFilter;
 import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ViewState;
@@ -63,8 +62,8 @@
             return mValue;
         }
     };
-    private PropertyAnimator.AnimatableProperty mProperty
-            = new PropertyAnimator.AnimatableProperty() {
+    private AnimatableProperty mProperty
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
new file mode 100644
index 0000000..ca2f713
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Debug;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.DozeHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class DozeScrimControllerTest extends SysuiTestCase {
+
+    private ScrimController mScrimController;
+    private DozeScrimController mDozeScrimController;
+
+    @Before
+    public void setup() {
+        mScrimController = mock(ScrimController.class);
+        // Make sure callbacks will be invoked to complete the lifecycle.
+        doAnswer(invocationOnMock -> {
+            ScrimController.Callback callback = invocationOnMock.getArgument(1);
+            callback.onStart();
+            callback.onDisplayBlanked();
+            callback.onFinished();
+            return null;
+        }).when(mScrimController).transitionTo(any(ScrimState.class),
+                any(ScrimController.Callback.class));
+
+        mDozeScrimController = new DozeScrimController(mScrimController, getContext());
+        mDozeScrimController.setDozing(true);
+    }
+
+    @Test
+    public void changesScrimControllerState() {
+        mDozeScrimController.pulse(mock(DozeHost.PulseCallback.class), 0);
+        verify(mScrimController).transitionTo(eq(ScrimState.PULSING),
+                any(ScrimController.Callback.class));
+    }
+
+    @Test
+    public void callsPulseCallback() {
+        DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class);
+        mDozeScrimController.pulse(callback, 0);
+
+        verify(callback).onPulseStarted();
+        mDozeScrimController.pulseOutNow();
+        verify(callback).onPulseFinished();
+    }
+
+    @Test
+    public void secondPulseIsSuppressed() {
+        DozeHost.PulseCallback callback1 = mock(DozeHost.PulseCallback.class);
+        DozeHost.PulseCallback callback2 = mock(DozeHost.PulseCallback.class);
+        mDozeScrimController.pulse(callback1, 0);
+        mDozeScrimController.pulse(callback2, 0);
+
+        verify(callback1, never()).onPulseFinished();
+        verify(callback2).onPulseFinished();
+    }
+
+    @Test
+    public void suppressesPulseIfNotDozing() {
+        mDozeScrimController.setDozing(false);
+        DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class);
+        mDozeScrimController.pulse(callback, 0);
+
+        verify(callback).onPulseFinished();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
new file mode 100644
index 0000000..b9f695b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.utils.os.FakeHandler;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ScrimControllerTest extends SysuiTestCase {
+
+    private SynchronousScrimController mScrimController;
+    private ScrimView mScrimBehind;
+    private ScrimView mScrimInFront;
+    private View mHeadsUpScrim;
+    private Consumer<Boolean> mScrimVisibilityCallback;
+    private Boolean mScrimVisibile;
+    private LightBarController mLightBarController;
+    private DozeParameters mDozeParamenters;
+    private WakeLock mWakeLock;
+    private boolean mAlwaysOnEnabled;
+
+    @Before
+    public void setup() {
+        mLightBarController = mock(LightBarController.class);
+        mScrimBehind = new ScrimView(getContext());
+        mScrimInFront = new ScrimView(getContext());
+        mHeadsUpScrim = mock(View.class);
+        mWakeLock = mock(WakeLock.class);
+        mAlwaysOnEnabled = true;
+        mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible;
+        mDozeParamenters = mock(DozeParameters.class);
+        when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
+        mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind,
+                mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters);
+    }
+
+    @Test
+    public void initialState() {
+        Assert.assertEquals("ScrimController should start initialized",
+                mScrimController.getState(), ScrimState.UNINITIALIZED);
+    }
+
+    @Test
+    public void transitionToKeyguard() {
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible without tint
+        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimTint(mScrimBehind, false /* tinted */);
+    }
+
+    @Test
+    public void transitionToAod() {
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible with tint
+        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimTint(mScrimBehind, true /* tinted */);
+        assertScrimTint(mScrimInFront, true /* tinted */);
+    }
+
+    @Test
+    public void transitionToPulsing() {
+        mScrimController.transitionTo(ScrimState.PULSING);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible with tint
+        // Pulse callback should have been invoked
+        assertScrimVisibility(false /* front */, true /* behind */);
+        assertScrimTint(mScrimBehind, true /* tinted */);
+    }
+
+    @Test
+    public void transitionToBouncer() {
+        mScrimController.transitionTo(ScrimState.BOUNCER);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible without tint
+        assertScrimVisibility(true /* front */, true /* behind */);
+        assertScrimTint(mScrimBehind, false /* tinted */);
+    }
+
+    @Test
+    public void transitionToUnlocked() {
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be transparent
+        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimTint(mScrimInFront, false /* tinted */);
+
+        // Back scrim should be visible after start dragging
+        mScrimController.setPanelExpansion(0.5f);
+        assertScrimVisibility(false /* front */, true /* behind */);
+    }
+
+    @Test
+    public void transitionToUnlockedFromAod() {
+        // Simulate unlock with fingerprint
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        // Immediately tinted after the transition starts
+        assertScrimTint(mScrimInFront, true /* tinted */);
+        assertScrimTint(mScrimBehind, true /* tinted */);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be transparent
+        // Neither scrims should be tinted anymore after the animation.
+        assertScrimVisibility(false /* front */, false /* behind */);
+        assertScrimTint(mScrimInFront, false /* tinted */);
+        assertScrimTint(mScrimBehind, false /* tinted */);
+    }
+
+    @Test
+    public void scrimBlanksBeforeLeavingAoD() {
+        // Simulate unlock with fingerprint
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        mScrimController.transitionTo(ScrimState.UNLOCKED,
+                new ScrimController.Callback() {
+                    @Override
+                    public void onDisplayBlanked() {
+                        // Front scrim should be black in the middle of the transition
+                        Assert.assertTrue("Scrim should be visible during transition. Alpha: "
+                                + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
+                        assertScrimTint(mScrimInFront, true /* tinted */);
+                        Assert.assertTrue("Scrim should be visible during transition.",
+                                mScrimVisibile);
+                    }
+                });
+        mScrimController.finishAnimationsImmediately();
+    }
+
+    @Test
+    public void testScrimCallback() {
+        int[] callOrder = {0, 0, 0};
+        int[] currentCall = {0};
+        mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+            @Override
+            public void onStart() {
+                callOrder[0] = ++currentCall[0];
+            }
+
+            @Override
+            public void onDisplayBlanked() {
+                callOrder[1] = ++currentCall[0];
+            }
+
+            @Override
+            public void onFinished() {
+                callOrder[2] = ++currentCall[0];
+            }
+        });
+        mScrimController.finishAnimationsImmediately();
+        Assert.assertEquals("onStart called in wrong order", 1, callOrder[0]);
+        Assert.assertEquals("onDisplayBlanked called in wrong order", 2, callOrder[1]);
+        Assert.assertEquals("onFinished called in wrong order", 3, callOrder[2]);
+    }
+
+    @Test
+    public void testScrimCallbacksWithoutAmbientDisplay() {
+        mAlwaysOnEnabled = false;
+        testScrimCallback();
+    }
+
+    @Test
+    public void testScrimCallbackCancelled() {
+        boolean[] cancelledCalled = {false};
+        mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+            @Override
+            public void onCancelled() {
+                cancelledCalled[0] = true;
+            }
+        });
+        mScrimController.transitionTo(ScrimState.PULSING);
+        Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]);
+    }
+
+    @Test
+    public void testHoldsWakeLock() {
+        mScrimController.transitionTo(ScrimState.AOD);
+        verify(mWakeLock, times(1)).acquire();
+        verify(mWakeLock, never()).release();
+        mScrimController.finishAnimationsImmediately();
+        verify(mWakeLock, times(1)).release();
+    }
+
+    private void assertScrimTint(ScrimView scrimView, boolean tinted) {
+        final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
+        final String name = scrimView == mScrimInFront ? "front" : "back";
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
+                tinted, viewIsTinted);
+    }
+
+    private void assertScrimVisibility(boolean inFront, boolean behind) {
+        Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
+                + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0);
+        Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
+                + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0);
+        Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile);
+    }
+
+    /**
+     * Special version of ScrimController where animations have 0 duration for test purposes.
+     */
+    private class SynchronousScrimController extends ScrimController {
+
+        private FakeHandler mHandler;
+
+        public SynchronousScrimController(LightBarController lightBarController,
+                ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
+                Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) {
+            super(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
+                    scrimVisibleListener, dozeParameters);
+            mHandler = new FakeHandler(Looper.myLooper());
+        }
+
+        public void finishAnimationsImmediately() {
+            boolean[] animationFinished = {false};
+            setOnAnimationFinished(()-> animationFinished[0] = true);
+
+            // Execute code that will trigger animations.
+            onPreDraw();
+
+            // Force finish screen blanking.
+            endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK);
+            mHandler.dispatchQueuedMessages();
+            // Force finish all animations.
+            endAnimation(mScrimBehind, TAG_KEY_ANIM);
+            endAnimation(mScrimInFront, TAG_KEY_ANIM);
+
+            if (!animationFinished[0]) {
+                throw new IllegalStateException("Animation never finished");
+            }
+        }
+
+        private void endAnimation(ScrimView scrimView, int tag) {
+            Animator animator = (Animator) scrimView.getTag(tag);
+            if (animator != null) {
+                animator.end();
+            }
+        }
+
+        @Override
+        protected Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        protected WakeLock createWakeLock() {
+            return mWakeLock;
+        }
+    }
+
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index f5d5d27..bd6af01 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4010,7 +4010,8 @@
     FIELD_AUTOFILL_NUM_IDS = 917;
 
     // ACTION: An autofill service was reqiested to save data
-    // Type TYPE_SUCCESS: The request succeeded
+    // Type TYPE_SUCCESS: The request succeeded right away
+    // Type TYPE_OPEN: The request succeeded but the service launched an IntentSender
     // Type TYPE_FAILURE: The request failed
     // Package: Package of app that was autofilled
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
index 7e94d7b..22d922b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
@@ -20,9 +20,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
-import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.annotation.NonNull;
@@ -49,7 +47,6 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityCache;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -65,6 +62,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -106,7 +104,7 @@
 
     int mFeedbackType;
 
-    Set<String> mPackageNames = new HashSet<>();
+    final Set<String> mPackageNames = new HashSet<>();
 
     boolean mIsDefault;
 
@@ -284,40 +282,98 @@
         return true;
     }
 
-    public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
-        mEventTypes = info.eventTypes;
-        mFeedbackType = info.feedbackType;
-        String[] packageNames = info.packageNames;
-        if (packageNames != null) {
-            mPackageNames.addAll(Arrays.asList(packageNames));
+    boolean setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+        boolean somethingChanged = false;
+
+        if (mEventTypes != info.eventTypes) {
+            mEventTypes = info.eventTypes;
+            somethingChanged = true;
         }
-        mNotificationTimeout = info.notificationTimeout;
-        mIsDefault = (info.flags & DEFAULT) != 0;
+
+        if (mFeedbackType != info.feedbackType) {
+            mFeedbackType = info.feedbackType;
+            somethingChanged = true;
+        }
+
+        final String[] oldPackageNames = mPackageNames.toArray(new String[mPackageNames.size()]);
+        if (!Arrays.equals(oldPackageNames, info.packageNames)) {
+            mPackageNames.clear();
+            if (info.packageNames != null) {
+                Collections.addAll(mPackageNames, info.packageNames);
+            }
+            somethingChanged = true;
+        }
+
+        if (mNotificationTimeout != info.notificationTimeout) {
+            mNotificationTimeout = info.notificationTimeout;
+            somethingChanged = true;
+        }
+
+        final boolean newIsDefault = (info.flags & DEFAULT) != 0;
+        if (mIsDefault != newIsDefault) {
+            mIsDefault = newIsDefault;
+            somethingChanged = true;
+        }
 
         if (supportsFlagForNotImportantViews(info)) {
-            if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
-                mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            } else {
-                mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+            somethingChanged |= updateFetchFlag(info.flags,
+                    AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+        }
+
+        somethingChanged |= updateFetchFlag(info.flags,
+                AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS);
+
+        final boolean newRequestTouchExplorationMode = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+        if (mRequestTouchExplorationMode != newRequestTouchExplorationMode) {
+            mRequestTouchExplorationMode = newRequestTouchExplorationMode;
+            somethingChanged = true;
+        }
+
+        final boolean newRequestFilterKeyEvents = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+        if (mRequestFilterKeyEvents != newRequestFilterKeyEvents) {
+            mRequestFilterKeyEvents = newRequestFilterKeyEvents;
+            somethingChanged = true;
+        }
+
+        final boolean newRetrieveInteractiveWindows = (info.flags
+                & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+        if (mRetrieveInteractiveWindows != newRetrieveInteractiveWindows) {
+            mRetrieveInteractiveWindows = newRetrieveInteractiveWindows;
+            somethingChanged = true;
+        }
+
+        final boolean newCaptureFingerprintGestures = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
+        if (mCaptureFingerprintGestures != newCaptureFingerprintGestures) {
+            mCaptureFingerprintGestures = newCaptureFingerprintGestures;
+            somethingChanged = true;
+        }
+
+        final boolean newRequestAccessibilityButton = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+        if (mRequestAccessibilityButton != newRequestAccessibilityButton) {
+            mRequestAccessibilityButton = newRequestAccessibilityButton;
+            somethingChanged = true;
+        }
+
+        return somethingChanged;
+    }
+
+    private boolean updateFetchFlag(int allFlags, int flagToUpdate) {
+        if ((allFlags & flagToUpdate) != 0) {
+            if ((mFetchFlags & flagToUpdate) == 0) {
+                mFetchFlags |= flagToUpdate;
+                return true;
+            }
+        } else {
+            if ((mFetchFlags & flagToUpdate) != 0) {
+                mFetchFlags &= ~flagToUpdate;
+                return true;
             }
         }
-
-        if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
-            mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-        } else {
-            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-        }
-
-        mRequestTouchExplorationMode = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
-        mRequestFilterKeyEvents = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
-        mRetrieveInteractiveWindows = (info.flags
-                & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
-        mCaptureFingerprintGestures = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
-        mRequestAccessibilityButton = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+        return false;
     }
 
     protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
@@ -349,14 +405,15 @@
                 // If the XML manifest had data to configure the service its info
                 // should be already set. In such a case update only the dynamically
                 // configurable properties.
+                final boolean serviceInfoChanged;
                 AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
                 if (oldInfo != null) {
                     oldInfo.updateDynamicallyConfigurableProperties(info);
-                    setDynamicallyConfigurableProperties(oldInfo);
+                    serviceInfoChanged = setDynamicallyConfigurableProperties(oldInfo);
                 } else {
-                    setDynamicallyConfigurableProperties(info);
+                    serviceInfoChanged = setDynamicallyConfigurableProperties(info);
                 }
-                mSystemSupport.onClientChange(true);
+                mSystemSupport.onClientChange(serviceInfoChanged);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3554448..8b5c85a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2400,7 +2400,8 @@
         private void announceNewUserIfNeeded() {
             synchronized (mLock) {
                 UserState userState = getCurrentUserStateLocked();
-                if (userState.isHandlingAccessibilityEvents()) {
+                if (userState.isHandlingAccessibilityEvents()
+                        && userState.isObservedEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT)) {
                     UserManager userManager = (UserManager) mContext.getSystemService(
                             Context.USER_SERVICE);
                     String message = mContext.getString(R.string.user_switched,
@@ -3157,13 +3158,21 @@
             if (mWindowsForAccessibilityCallback == null) {
                 return;
             }
+            final int userId;
+            synchronized (mLock) {
+                userId = mCurrentUserId;
+                final UserState userState = getUserStateLocked(userId);
+                if (!userState.isObservedEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) {
+                    return;
+                }
+            }
             final long identity = Binder.clearCallingIdentity();
             try {
                 // Let the client know the windows changed.
                 AccessibilityEvent event = AccessibilityEvent.obtain(
                         AccessibilityEvent.TYPE_WINDOWS_CHANGED);
                 event.setEventTime(SystemClock.uptimeMillis());
-                sendAccessibilityEvent(event, mCurrentUserId);
+                sendAccessibilityEvent(event, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -3368,6 +3377,10 @@
             mUserId = userId;
         }
 
+        public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+            return (mLastSentRelevantEventTypes & type) != 0;
+        }
+
         public int getClientState() {
             int clientState = 0;
             final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 3419b80..62017e8 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -791,7 +791,7 @@
      */
     private void sendAccessibilityEvent(int type) {
         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
-        if (accessibilityManager.isEnabled()) {
+        if (accessibilityManager.isObservedEventType(type)) {
             AccessibilityEvent event = AccessibilityEvent.obtain(type);
             event.setWindowId(mAms.getActiveWindowId());
             accessibilityManager.sendAccessibilityEvent(event);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index af55807..831c488 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -26,6 +26,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.content.ServiceConnection;
 import android.os.Handler;
 import android.os.IBinder;
@@ -100,7 +101,8 @@
                 @NonNull String servicePackageName);
         void onFillRequestFailure(@Nullable CharSequence message,
                 @NonNull String servicePackageName);
-        void onSaveRequestSuccess(@NonNull String servicePackageName);
+        void onSaveRequestSuccess(@NonNull String servicePackageName,
+                @Nullable IntentSender intentSender);
         void onSaveRequestFailure(@Nullable CharSequence message,
                 @NonNull String servicePackageName);
         void onServiceDied(RemoteFillService service);
@@ -308,10 +310,11 @@
         });
     }
 
-    private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) {
+    private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest,
+            IntentSender intentSender) {
         mHandler.getHandler().post(() -> {
             if (handleResponseCallbackCommon(pendingRequest)) {
-                mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName());
+                mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender);
             }
         });
     }
@@ -624,12 +627,13 @@
 
             mCallback = new ISaveCallback.Stub() {
                 @Override
-                public void onSuccess() {
+                public void onSuccess(IntentSender intentSender) {
                     if (!finish()) return;
 
                     final RemoteFillService remoteService = getService();
                     if (remoteService != null) {
-                        remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this);
+                        remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this,
+                                intentSender);
                     }
                 }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 9741486..99b92b9 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -561,7 +561,8 @@
 
     // FillServiceCallbacks
     @Override
-    public void onSaveRequestSuccess(@NonNull String servicePackageName) {
+    public void onSaveRequestSuccess(@NonNull String servicePackageName,
+            @Nullable IntentSender intentSender) {
         synchronized (mLock) {
             mIsSaving = false;
 
@@ -572,8 +573,12 @@
             }
         }
         LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
-                .setType(MetricsEvent.TYPE_SUCCESS);
+                .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
         mMetricsLogger.write(log);
+        if (intentSender != null) {
+            if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
+            startIntentSender(intentSender);
+        }
 
         // Nothing left to do...
         removeSelf();
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 3904fc9..ca15249 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -46,16 +46,15 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -81,6 +80,7 @@
 import java.util.Random;
 import java.util.TimeZone;
 import java.util.TreeSet;
+import java.util.function.Predicate;
 
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.RTC;
@@ -88,11 +88,17 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
 
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
 class AlarmManagerService extends SystemService {
     private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
     private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +137,10 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
-    IAppOpsService mAppOpsService;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
-    ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
-    SparseBooleanArray mForegroundUids = new SparseBooleanArray();
     // List of alarms per uid deferred due to user applied background restrictions on the source app
     SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
     long mNativeData;
@@ -184,12 +187,6 @@
     int mSystemUiUid;
 
     /**
-     * The current set of user whitelisted apps for device idle mode, meaning these are allowed
-     * to freely schedule alarms.
-     */
-    int[] mDeviceIdleUserWhitelist = new int[0];
-
-    /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
      * used to determine the earliest we can dispatch the next such alarm. Times are in the
      * 'elapsed' timebase.
@@ -223,6 +220,8 @@
     private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
             new SparseArray<>();
 
+    private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
@@ -757,6 +756,9 @@
     public AlarmManagerService(Context context) {
         super(context);
         mConstants = new Constants(mHandler);
+
+        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+        mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
     }
 
     static long convertToElapsed(long when, int type) {
@@ -894,17 +896,48 @@
         deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
     }
 
-    void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+    /**
+     * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+     * restricted.
+     *
+     * This is only called when the global "force all apps-standby" flag changes or when the
+     * power save whitelist changes, so it's okay to be slow.
+     */
+    void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
         final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
-        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
-            final int uid = mPendingBackgroundAlarms.keyAt(i);
-            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
-            if (UserHandle.getAppId(uid) == appId) {
-                alarmsToDeliver.addAll(alarmsForUid);
-                mPendingBackgroundAlarms.removeAt(i);
+
+        findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+                mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+        if (alarmsToDeliver.size() > 0) {
+            deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+        }
+    }
+
+    @VisibleForTesting
+    static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+            SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+            Predicate<Alarm> isBackgroundRestricted) {
+
+        for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+            final int uid = pendingAlarms.keyAt(uidIndex);
+            final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+            for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+                final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+                if (isBackgroundRestricted.test(alarm)) {
+                    continue;
+                }
+
+                unrestrictedAlarms.add(alarm);
+                alarmsForUid.remove(alarmIndex);
+            }
+
+            if (alarmsForUid.size() == 0) {
+                pendingAlarms.removeAt(uidIndex);
             }
         }
-        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
     }
 
     private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1267,8 @@
         } catch (RemoteException e) {
             // ignored; both services live in system_server
         }
-        mAppOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
         publishBinderService(Context.ALARM_SERVICE, mService);
-        publishLocalService(LocalService.class, new LocalService());
+        mForceAppStandbyTracker.start();
     }
 
     @Override
@@ -1247,13 +1278,6 @@
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
             mLocalDeviceIdleController
                     = LocalServices.getService(DeviceIdleController.LocalService.class);
-            try {
-                mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
-                        new AppOpsWatcher());
-            } catch (RemoteException rexc) {
-                // Shouldn't happen as they are in the same process.
-                Slog.e(TAG, "AppOps service not reachable", rexc);
-            }
         }
     }
 
@@ -1582,8 +1606,7 @@
             // timing restrictions.
             } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                     || callingUid == mSystemUiUid
-                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
-                            UserHandle.getAppId(callingUid)) >= 0)) {
+                    || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
                 flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
             }
@@ -1660,24 +1683,14 @@
         }
     };
 
-    public final class LocalService {
-        public void setDeviceIdleUserWhitelist(int[] appids) {
-            setDeviceIdleUserWhitelistImpl(appids);
-        }
-    }
-
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
             mConstants.dump(pw);
             pw.println();
 
-            pw.print("  Foreground uids: [");
-            for (int i = 0; i < mForegroundUids.size(); i++) {
-                if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
-            }
-            pw.println("]");
-            pw.println("  Forced app standby packages: " + mForcedAppStandbyPackages);
+            mForceAppStandbyTracker.dump(pw, "  ");
+
             final long nowRTC = System.currentTimeMillis();
             final long nowELAPSED = SystemClock.elapsedRealtime();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1730,6 @@
             pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
             pw.println();
             pw.print("  Num time change events: "); pw.println(mNumTimeChanged);
-            pw.println("  mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
 
             pw.println();
             pw.println("  Next alarm clock information: ");
@@ -1990,15 +2002,8 @@
 
             mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
 
-            final int foregroundUidsSize = mForegroundUids.size();
-            for (int i = 0; i < foregroundUidsSize; i++) {
-                if (mForegroundUids.valueAt(i)) {
-                    proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
-                }
-            }
-            for (String pkg : mForcedAppStandbyPackages) {
-                proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
-            }
+            mForceAppStandbyTracker.dumpProto(proto,
+                    AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
 
             proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
             if (!mInteractive) {
@@ -2022,9 +2027,6 @@
             proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
                     nowElapsed - mLastWakeupSet);
             proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
-            for (int i : mDeviceIdleUserWhitelist) {
-                proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
-            }
 
             final TreeSet<Integer> users = new TreeSet<>();
             final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2268,6 @@
         }
     }
 
-    void setDeviceIdleUserWhitelistImpl(int[] appids) {
-        synchronized (mLock) {
-            // appids are sorted, just send pending alarms for any new appids added to the whitelist
-            int i = 0, j = 0;
-            while (i < appids.length) {
-                while (j < mDeviceIdleUserWhitelist.length
-                        && mDeviceIdleUserWhitelist[j] < appids[i]) {
-                    j++;
-                }
-                if (j < mDeviceIdleUserWhitelist.length
-                        && appids[i] != mDeviceIdleUserWhitelist[j]) {
-                    if (DEBUG_BG_LIMIT) {
-                        Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
-                    }
-                    sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
-                }
-                i++;
-            }
-            mDeviceIdleUserWhitelist = appids;
-        }
-    }
-
     AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
         synchronized (mLock) {
             return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2690,7 @@
         final String sourcePackage =
                 (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
         final int sourceUid = alarm.creatorUid;
-        return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
-                && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
-                < 0;
+        return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
     }
 
     private native long init();
@@ -2859,7 +2837,8 @@
         }
     }
 
-    private static class Alarm {
+    @VisibleForTesting
+    static class Alarm {
         public final int type;
         public final long origWhen;
         public final boolean wakeup;
@@ -3073,6 +3052,11 @@
         for (int i=0; i<triggerList.size(); i++) {
             Alarm alarm = triggerList.get(i);
             final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
+            if (alarm.wakeup) {
+              Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName);
+            } else {
+              Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName);
+            }
             try {
                 if (localLOGV) {
                     Slog.v(TAG, "sending alarm " + alarm);
@@ -3092,6 +3076,7 @@
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
 
@@ -3476,17 +3461,10 @@
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
-                mForegroundUids.delete(uid);
             }
         }
 
         @Override public void onUidActive(int uid) {
-            synchronized (mLock) {
-                if (!mForegroundUids.get(uid)) {
-                    mForegroundUids.put(uid, true);
-                    sendPendingBackgroundAlarmsLocked(uid, null);
-                }
-            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3472,6 @@
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
-                mForegroundUids.delete(uid);
             }
         }
 
@@ -3502,27 +3479,29 @@
         }
     };
 
-    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+    private final Listener mForceAppStandbyListener = new Listener() {
         @Override
-        public void opChanged(int op, int uid, String packageName) throws RemoteException {
+        public void unblockAllUnrestrictedAlarms() {
             synchronized (mLock) {
-                final int mode = mAppOpsService.checkOperation(op, uid, packageName);
-                if (DEBUG_BG_LIMIT) {
-                    Slog.d(TAG,
-                            "Appop changed for " + uid + ", " + packageName + " to " + mode);
-                }
-                final boolean changed;
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    changed = mForcedAppStandbyPackages.add(packageName);
-                } else {
-                    changed = mForcedAppStandbyPackages.remove(packageName);
-                }
-                if (changed && mode == AppOpsManager.MODE_ALLOWED) {
-                    sendPendingBackgroundAlarmsLocked(uid, packageName);
-                }
+                sendAllUnrestrictedPendingBackgroundAlarmsLocked();
             }
         }
-    }
+
+        @Override
+        public void unblockAlarmsForUid(int uid) {
+            synchronized (mLock) {
+                sendPendingBackgroundAlarmsLocked(uid, null);
+            }
+        }
+
+        @Override
+        public void unblockAlarmsForUidPackage(int uid, String packageName) {
+            synchronized (mLock) {
+                sendPendingBackgroundAlarmsLocked(uid, packageName);
+            }
+        }
+    };
 
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index c34c30c..04279a3 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -2150,31 +2150,26 @@
                                           (int)((onDuration / (1000 * 60)) % 60),
                                           (int)((onDuration / 1000) % 60),
                                           (int)(onDuration % 1000));
-                writer.println("  time since enabled: " + onDurationString + "\n");
+                writer.println("  time since enabled: " + onDurationString);
             }
 
             if (mActiveLogs.size() == 0) {
-                writer.println("Bluetooth never enabled!");
+                writer.println("\nBluetooth never enabled!");
             } else {
-                writer.println("Enable log:");
+                writer.println("\nEnable log:");
                 for (ActiveLog log : mActiveLogs) {
                     writer.println("  " + log);
                 }
             }
 
-            writer.println("Bluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+            writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
             if (mCrashes == CRASH_LOG_MAX_SIZE) writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
             for (Long time : mCrashTimestamps) {
               writer.println("  " + timeToLog(time.longValue()));
             }
 
-            String bleAppString = "No BLE Apps registered.";
-            if (mBleApps.size() == 1) {
-                bleAppString = "1 BLE App registered:";
-            } else if (mBleApps.size() > 1) {
-                bleAppString = mBleApps.size() + " BLE Apps registered:";
-            }
-            writer.println("\n" + bleAppString);
+            writer.println("\n" + mBleApps.size() + " BLE app" +
+                            (mBleApps.size() == 1 ? "" : "s") + "registered");
             for (ClientDeathRecipient app : mBleApps.values()) {
                 writer.println("  " + app.getPackageName());
             }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 0921a00..d7aeb8c 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@
     private PowerManagerInternal mLocalPowerManager;
     private PowerManager mPowerManager;
     private ConnectivityService mConnectivityService;
-    private AlarmManagerService.LocalService mLocalAlarmManager;
     private INetworkPolicyManager mNetworkPolicyManager;
     private SensorManager mSensorManager;
     private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@
                 mGoingIdleWakeLock.setReferenceCounted(true);
                 mConnectivityService = (ConnectivityService)ServiceManager.getService(
                         Context.CONNECTIVITY_SERVICE);
-                mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
                 mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                         ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
                 mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@
 
                 mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
-                mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
 
+                passWhiteListToForceAppStandbyTrackerLocked();
                 updateInteractivityLocked();
             }
             updateConnectivityState(null);
@@ -2477,13 +2475,7 @@
             }
             mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
         }
-        if (mLocalAlarmManager != null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Setting alarm whitelist to "
-                        + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
-            }
-            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
-        }
+        passWhiteListToForceAppStandbyTrackerLocked();
     }
 
     private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@
             }
             mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
         }
+        passWhiteListToForceAppStandbyTrackerLocked();
     }
 
     private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@
         getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
     }
 
+    private void passWhiteListToForceAppStandbyTrackerLocked() {
+        ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+                mPowerSaveWhitelistAllAppIdArray,
+                mTempWhitelistAppIdArray);
+    }
+
     void readConfigFileLocked() {
         if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
         mPowerSaveWhitelistUserApps.clear();
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 5dd3ee0..61d3833 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -16,13 +16,18 @@
 package com.android.server;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
 import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
@@ -30,21 +35,36 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Pair;
-import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
 
+import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
 
 /**
- * Class to track OP_RUN_ANY_IN_BACKGROUND, UID foreground state and "force all app standby".
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
  *
- * TODO Clean up cache when a user is deleted.
- * TODO Add unit tests. b/68769804.
+ * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
+ *    each callback. For example, even when an UID comes into the foreground, if it wasn't
+ *    originally restricted, then there's no need to send an event.
+ *    Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ *
+ * Test:
+   atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
  */
 public class ForceAppStandbyTracker {
     private static final String TAG = "ForceAppStandbyTracker";
@@ -55,22 +75,32 @@
     private final Object mLock = new Object();
     private final Context mContext;
 
+    @VisibleForTesting
+    static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+    IActivityManager mIActivityManager;
     AppOpsManager mAppOpsManager;
     IAppOpsService mAppOpsService;
     PowerManagerInternal mPowerManagerInternal;
 
-    private final Handler mCallbackHandler;
+    private final MyHandler mHandler;
 
     /**
      * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
      */
     @GuardedBy("mLock")
-    final ArraySet<Pair<Integer, String>> mForcedAppStandbyUidPackages = new ArraySet<>();
+    final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
 
     @GuardedBy("mLock")
     final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
 
     @GuardedBy("mLock")
+    private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+    @GuardedBy("mLock")
+    private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+    @GuardedBy("mLock")
     final ArraySet<Listener> mListeners = new ArraySet<>();
 
     @GuardedBy("mLock")
@@ -80,16 +110,116 @@
     boolean mForceAllAppsStandby;
 
     public static abstract class Listener {
-        public void onRestrictionChanged(int uid, @Nullable String packageName) {
+        /**
+         * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+         */
+        private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+                int uid, @NonNull String packageName) {
+            updateJobsForUidPackage(uid, packageName);
+
+            if (!sender.areAlarmsRestricted(uid, packageName)) {
+                unblockAlarmsForUidPackage(uid, packageName);
+            }
         }
 
-        public void onGlobalRestrictionChanged() {
+        /**
+         * This is called when the foreground state changed for a UID.
+         */
+        private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+            updateJobsForUid(uid);
+
+            if (sender.isInForeground(uid)) {
+                unblockAlarmsForUid(uid);
+            }
+        }
+
+        /**
+         * This is called when an app-id(s) is removed from the power save whitelist.
+         */
+        private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+            unblockAllUnrestrictedAlarms();
+        }
+
+        /**
+         * This is called when the power save whitelist changes, excluding the
+         * {@link #onPowerSaveUnwhitelisted} case.
+         */
+        private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+        }
+
+        /**
+         * This is called when the temp whitelist changes.
+         */
+        private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+            // TODO This case happens rather frequently; consider optimizing and update jobs
+            // only for affected app-ids.
+
+            updateAllJobs();
+        }
+
+        /**
+         * This is called when the global "force all apps standby" flag changes.
+         */
+        private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+
+            if (!sender.isForceAllAppsStandbyEnabled()) {
+                unblockAllUnrestrictedAlarms();
+            }
+        }
+
+        /**
+         * Called when the job restrictions for multiple UIDs might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateAllJobs() {
+        }
+
+        /**
+         * Called when the job restrictions for a UID might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateJobsForUid(int uid) {
+        }
+
+        /**
+         * Called when the job restrictions for a UID - package might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateJobsForUidPackage(int uid, String packageName) {
+        }
+
+        /**
+         * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+         * manager should re-evaluate all restrictions for all blocked jobs.
+         */
+        public void unblockAllUnrestrictedAlarms() {
+        }
+
+        /**
+         * Called when all jobs for a specific UID are unblocked.
+         */
+        public void unblockAlarmsForUid(int uid) {
+        }
+
+        /**
+         * Called when all alarms for a specific UID - package are unblocked.
+         */
+        public void unblockAlarmsForUidPackage(int uid, String packageName) {
         }
     }
 
-    private ForceAppStandbyTracker(Context context) {
+    @VisibleForTesting
+    ForceAppStandbyTracker(Context context, Looper looper) {
         mContext = context;
-        mCallbackHandler = FgThread.getHandler();
+        mHandler = new MyHandler(looper);
+    }
+
+    private ForceAppStandbyTracker(Context context) {
+        this(context, FgThread.get().getLooper());
     }
 
     /**
@@ -112,45 +242,65 @@
             }
             mStarted = true;
 
-            mAppOpsManager = Preconditions.checkNotNull(
-                    mContext.getSystemService(AppOpsManager.class));
-            mAppOpsService = Preconditions.checkNotNull(
-                    IAppOpsService.Stub.asInterface(
-                            ServiceManager.getService(Context.APP_OPS_SERVICE)));
-            mPowerManagerInternal = Preconditions.checkNotNull(
-                    LocalServices.getService(PowerManagerInternal.class));
+            mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+            mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+            mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+            mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
 
             try {
-                ActivityManager.getService().registerUidObserver(new UidObserver(),
+                mIActivityManager.registerUidObserver(new UidObserver(),
                         ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
                                 | ActivityManager.UID_OBSERVER_ACTIVE,
                         ActivityManager.PROCESS_STATE_UNKNOWN, null);
-                mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
+                mAppOpsService.startWatchingMode(TARGET_OP, null,
                         new AppOpsWatcher());
             } catch (RemoteException e) {
                 // shouldn't happen.
             }
 
-            mPowerManagerInternal.registerLowPowerModeObserver(
-                    ServiceType.FORCE_ALL_APPS_STANDBY,
-                    state -> updateForceAllAppsStandby(state.batterySaverEnabled));
-
-            updateForceAllAppsStandby(
-                    mPowerManagerInternal.getLowPowerState(ServiceType.FORCE_ALL_APPS_STANDBY)
-                            .batterySaverEnabled);
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_USER_REMOVED);
+            mContext.registerReceiver(new MyReceiver(), filter);
 
             refreshForcedAppStandbyUidPackagesLocked();
+
+            mPowerManagerInternal.registerLowPowerModeObserver(
+                    ServiceType.FORCE_ALL_APPS_STANDBY,
+                    (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+            updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+                    ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
         }
     }
 
+    @VisibleForTesting
+    AppOpsManager injectAppOpsManager() {
+        return mContext.getSystemService(AppOpsManager.class);
+    }
+
+    @VisibleForTesting
+    IAppOpsService injectIAppOpsService() {
+        return IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
+    }
+
+    @VisibleForTesting
+    IActivityManager injectIActivityManager() {
+        return ActivityManager.getService();
+    }
+
+    @VisibleForTesting
+    PowerManagerInternal injectPowerManagerInternal() {
+        return LocalServices.getService(PowerManagerInternal.class);
+    }
+
     /**
-     * Update {@link #mForcedAppStandbyUidPackages} with the current app ops state.
+     * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
      */
     private void refreshForcedAppStandbyUidPackagesLocked() {
-        final int op = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
-
-        mForcedAppStandbyUidPackages.clear();
-        final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(new int[] {op});
+        mRunAnyRestrictedPackages.clear();
+        final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+                new int[] {TARGET_OP});
 
         if (ops == null) {
             return;
@@ -162,31 +312,38 @@
 
             for (int j = 0; j < entries.size(); j++) {
                 AppOpsManager.OpEntry ent = entries.get(j);
-                if (ent.getOp() != op) {
+                if (ent.getOp() != TARGET_OP) {
                     continue;
                 }
                 if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
-                    mForcedAppStandbyUidPackages.add(Pair.create(
+                    mRunAnyRestrictedPackages.add(Pair.create(
                             pkg.getUid(), pkg.getPackageName()));
                 }
             }
         }
     }
 
-    boolean isRunAnyInBackgroundAppOpRestricted(int uid, @NonNull String packageName) {
-        try {
-            return mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                    uid, packageName) != AppOpsManager.MODE_ALLOWED;
-        } catch (RemoteException e) {
-            return false; // shouldn't happen.
+    /**
+     * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+     */
+    void updateForceAllAppsStandby(boolean enable) {
+        synchronized (mLock) {
+            if (enable == mForceAllAppsStandby) {
+                return;
+            }
+            mForceAllAppsStandby = enable;
+
+            mHandler.notifyForceAllAppsStandbyChanged();
         }
     }
 
     private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
-        // TODO Maybe we should switch to indexOf(Pair.create()) if the array size is too big.
-        final int size = mForcedAppStandbyUidPackages.size();
+        final int size = mRunAnyRestrictedPackages.size();
+        if (size > 8) {
+            return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+        }
         for (int i = 0; i < size; i++) {
-            final Pair<Integer, String> pair = mForcedAppStandbyUidPackages.valueAt(i);
+            final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
 
             if ((pair.first == uid) && packageName.equals(pair.second)) {
                 return i;
@@ -196,13 +353,16 @@
     }
 
     /**
-     * @return whether a uid package-name pair is in mForcedAppStandbyUidPackages.
+     * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
      */
-    boolean isUidPackageRestrictedLocked(int uid, @NonNull String packageName) {
+    boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
         return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
     }
 
-    boolean updateRestrictedUidPackageLocked(int uid, @NonNull String packageName,
+    /**
+     * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+     */
+    boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
             boolean restricted) {
         final int index =  findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
         final boolean wasRestricted = index >= 0;
@@ -210,13 +370,16 @@
             return false;
         }
         if (restricted) {
-            mForcedAppStandbyUidPackages.add(Pair.create(uid, packageName));
+            mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
         } else {
-            mForcedAppStandbyUidPackages.removeAt(index);
+            mRunAnyRestrictedPackages.removeAt(index);
         }
         return true;
     }
 
+    /**
+     * Puts a UID to {@link #mForegroundUids}.
+     */
     void uidToForeground(int uid) {
         synchronized (mLock) {
             if (!UserHandle.isApp(uid)) {
@@ -228,10 +391,13 @@
                 return;
             }
             mForegroundUids.put(uid, true);
-            notifyForUidPackage(uid, null);
+            mHandler.notifyUidForegroundStateChanged(uid);
         }
     }
 
+    /**
+     * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+     */
     void uidToBackground(int uid, boolean remove) {
         synchronized (mLock) {
             if (!UserHandle.isApp(uid)) {
@@ -247,13 +413,11 @@
             } else {
                 mForegroundUids.put(uid, false);
             }
-            notifyForUidPackage(uid, null);
+            mHandler.notifyUidForegroundStateChanged(uid);
         }
     }
 
-    // Event handlers
-
-    final class UidObserver extends IUidObserver.Stub {
+    private final class UidObserver extends IUidObserver.Stub {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
         }
 
@@ -277,11 +441,28 @@
     private final class AppOpsWatcher extends IAppOpsCallback.Stub {
         @Override
         public void opChanged(int op, int uid, String packageName) throws RemoteException {
+            boolean restricted = false;
+            try {
+                restricted = mAppOpsService.checkOperation(TARGET_OP,
+                        uid, packageName) != AppOpsManager.MODE_ALLOWED;
+            } catch (RemoteException e) {
+                // Shouldn't happen
+            }
             synchronized (mLock) {
-                final boolean restricted = isRunAnyInBackgroundAppOpRestricted(uid, packageName);
+                if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+                    mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+                }
+            }
+        }
+    }
 
-                if (updateRestrictedUidPackageLocked(uid, packageName, restricted)) {
-                    notifyForUidPackage(uid, packageName);
+    private final class MyReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId > 0) {
+                    mHandler.doUserRemoved(userId);
                 }
             }
         }
@@ -293,31 +474,183 @@
         }
     }
 
-    void notifyForUidPackage(int uid, String packageName) {
-        mCallbackHandler.post(() -> {
-            for (Listener l : cloneListeners()) {
-                l.onRestrictionChanged(uid, packageName);
-            }
-        });
-    }
+    private class MyHandler extends Handler {
+        private static final int MSG_UID_STATE_CHANGED = 1;
+        private static final int MSG_RUN_ANY_CHANGED = 2;
+        private static final int MSG_ALL_UNWHITELISTED = 3;
+        private static final int MSG_ALL_WHITELIST_CHANGED = 4;
+        private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
+        private static final int MSG_FORCE_ALL_CHANGED = 6;
 
-    void notifyGlobal() {
-        mCallbackHandler.post(() -> {
-            for (Listener l : cloneListeners()) {
-                l.onGlobalRestrictionChanged();
-            }
-        });
-    }
+        private static final int MSG_USER_REMOVED = 7;
 
-    void updateForceAllAppsStandby(boolean forceAllAppsStandby) {
-        synchronized (mLock) {
-            if (mForceAllAppsStandby == forceAllAppsStandby) {
-                return;
-            }
-            mForceAllAppsStandby = forceAllAppsStandby;
-            Slog.i(TAG, "Force all app standby: " + mForceAllAppsStandby);
-            notifyGlobal();
+        public MyHandler(Looper looper) {
+            super(looper);
         }
+
+        public void notifyUidForegroundStateChanged(int uid) {
+            obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
+        }
+        public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
+            obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
+        }
+
+        public void notifyAllUnwhitelisted() {
+            obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+        }
+
+        public void notifyAllWhitelistChanged() {
+            obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+        }
+
+        public void notifyTempWhitelistChanged() {
+            obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+        }
+
+        public void notifyForceAllAppsStandbyChanged() {
+            obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+        }
+
+        public void doUserRemoved(int userId) {
+            obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+        }
+
+        @Override
+        public void dispatchMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USER_REMOVED:
+                    handleUserRemoved(msg.arg1);
+                    return;
+            }
+
+            // Only notify the listeners when started.
+            synchronized (mLock) {
+                if (!mStarted) {
+                    return;
+                }
+            }
+            final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+            switch (msg.what) {
+                case MSG_UID_STATE_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onUidForegroundStateChanged(sender, msg.arg1);
+                    }
+                    return;
+                case MSG_RUN_ANY_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+                    }
+                    return;
+                case MSG_ALL_UNWHITELISTED:
+                    for (Listener l : cloneListeners()) {
+                        l.onPowerSaveUnwhitelisted(sender);
+                    }
+                    return;
+                case MSG_ALL_WHITELIST_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onPowerSaveWhitelistedChanged(sender);
+                    }
+                    return;
+                case MSG_TEMP_WHITELIST_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onTempPowerSaveWhitelistChanged(sender);
+                    }
+                    return;
+                case MSG_FORCE_ALL_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onForceAllAppsStandbyChanged(sender);
+                    }
+                    return;
+                case MSG_USER_REMOVED:
+                    handleUserRemoved(msg.arg1);
+                    return;
+            }
+        }
+    }
+
+    void handleUserRemoved(int removedUserId) {
+        synchronized (mLock) {
+            for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+                final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+                final int uid = pair.first;
+                final int userId = UserHandle.getUserId(uid);
+
+                if (userId == removedUserId) {
+                    mRunAnyRestrictedPackages.removeAt(i);
+                }
+            }
+            for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+                final int uid = mForegroundUids.keyAt(i);
+                final int userId = UserHandle.getUserId(uid);
+
+                if (userId == removedUserId) {
+                    mForegroundUids.removeAt(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by device idle controller to update the power save whitelists.
+     */
+    public void setPowerSaveWhitelistAppIds(
+            int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+        synchronized (mLock) {
+            final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+            final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+            mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+            mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+            if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+                mHandler.notifyAllUnwhitelisted();
+            } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+                mHandler.notifyAllWhitelistChanged();
+            }
+
+            if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+                mHandler.notifyTempWhitelistChanged();
+            }
+
+        }
+    }
+
+    /**
+     * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+     * that's not in a sorted app-id array {@code newArray}.
+     */
+    @VisibleForTesting
+    static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+        int i1 = 0;
+        int i2 = 0;
+        boolean prevFinished;
+        boolean newFinished;
+
+        for (;;) {
+            prevFinished = i1 >= prevArray.length;
+            newFinished = i2 >= newArray.length;
+            if (prevFinished || newFinished) {
+                break;
+            }
+            int a1 = prevArray[i1];
+            int a2 = newArray[i2];
+
+            if (a1 == a2) {
+                i1++;
+                i2++;
+                continue;
+            }
+            if (a1 < a2) {
+                // prevArray has an element that's not in a2.
+                return true;
+            }
+            i2++;
+        }
+        if (prevFinished) {
+            return false;
+        }
+        return newFinished;
     }
 
     // Public interface.
@@ -332,21 +665,51 @@
     }
 
     /**
-     * Whether force-app-standby is effective for a UID package-name.
+     * @return whether alarms should be restricted for a UID package-name.
      */
-    public boolean isRestricted(int uid, @NonNull String packageName) {
+    public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+    }
+
+    /**
+     * @return whether jobs should be restricted for a UID package-name.
+     */
+    public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+    }
+
+    /**
+     * @return whether force-app-standby is effective for a UID package-name.
+     */
+    private boolean isRestricted(int uid, @NonNull String packageName,
+            boolean useTempWhitelistToo) {
         if (isInForeground(uid)) {
             return false;
         }
         synchronized (mLock) {
+            // Whitelisted?
+            final int appId = UserHandle.getAppId(uid);
+            if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+                return false;
+            }
+            if (useTempWhitelistToo &&
+                    ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+                return false;
+            }
+
             if (mForceAllAppsStandby) {
                 return true;
             }
-            return isUidPackageRestrictedLocked(uid, packageName);
+
+            return isRunAnyRestrictedLocked(uid, packageName);
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
+    /**
+     * @return whether a UID is in the foreground or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
     public boolean isInForeground(int uid) {
         if (!UserHandle.isApp(uid)) {
             return true;
@@ -356,31 +719,120 @@
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
-    public boolean isForceAllAppsStandbyEnabled() {
+    /**
+     * @return whether force all apps standby is enabled or not.
+     *
+     * Note clients normally shouldn't need to access it.
+     */
+    boolean isForceAllAppsStandbyEnabled() {
         synchronized (mLock) {
             return mForceAllAppsStandby;
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
+    /**
+     * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
     public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
         synchronized (mLock) {
-            return !isUidPackageRestrictedLocked(uid, packageName);
+            return !isRunAnyRestrictedLocked(uid, packageName);
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
-    public SparseBooleanArray getForegroudUids() {
+    /**
+     * @return whether a UID is in the user / system defined power-save whitelist or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isUidPowerSaveWhitelisted(int uid) {
         synchronized (mLock) {
-            return mForegroundUids.clone();
+            return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
-    public ArraySet<Pair<Integer, String>> getRestrictedUidPackages() {
+    /**
+     * @return whether a UID is in the temp power-save whitelist or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isUidTempPowerSaveWhitelisted(int uid) {
         synchronized (mLock) {
-            return new ArraySet(mForcedAppStandbyUidPackages);
+            return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+        }
+    }
+
+    public void dump(PrintWriter pw, String indent) {
+        synchronized (mLock) {
+            pw.print(indent);
+            pw.print("Force all apps standby: ");
+            pw.println(isForceAllAppsStandbyEnabled());
+
+            pw.print(indent);
+            pw.print("Foreground uids: [");
+
+            String sep = "";
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    pw.print(sep);
+                    pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+                    sep = " ";
+                }
+            }
+            pw.println("]");
+
+            pw.print(indent);
+            pw.print("Whitelist appids: ");
+            pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+            pw.print(indent);
+            pw.print("Temp whitelist appids: ");
+            pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+            pw.print(indent);
+            pw.println("Restricted packages:");
+            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+                pw.print(indent);
+                pw.print("  ");
+                pw.print(UserHandle.formatUid(uidAndPackage.first));
+                pw.print(" ");
+                pw.print(uidAndPackage.second);
+                pw.println();
+            }
+        }
+    }
+
+    public void dumpProto(ProtoOutputStream proto, long fieldId) {
+        synchronized (mLock) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+                            mForegroundUids.keyAt(i));
+                }
+            }
+
+            for (int appId : mPowerWhitelistedAllAppIds) {
+                proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+            }
+
+            for (int appId : mTempWhitelistedAppIds) {
+                proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+            }
+
+            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+                final long token2 = proto.start(
+                        ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+                proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+                proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+                        uidAndPackage.second);
+                proto.end(token2);
+            }
+            proto.end(token);
         }
     }
 }
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index 1ad1404..2c24798 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -69,13 +69,10 @@
     private static final String ACTION_POLL =
             "com.android.server.NetworkTimeUpdateService.action.POLL";
 
-    private static final int NETWORK_CHANGE_EVENT_DELAY_MS = 1000;
-    private static int POLL_REQUEST = 0;
+    private static final int POLL_REQUEST = 0;
 
     private static final long NOT_SET = -1;
     private long mNitzTimeSetTime = NOT_SET;
-    // TODO: Have a way to look up the timezone we are in
-    private long mNitzZoneSetTime = NOT_SET;
     private Network mDefaultNetwork = null;
 
     private Context mContext;
@@ -144,7 +141,6 @@
     private void registerForTelephonyIntents() {
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
-        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
         mContext.registerReceiver(mNitzReceiver, intentFilter);
     }
 
@@ -257,8 +253,6 @@
             if (DBG) Log.d(TAG, "Received " + action);
             if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
                 mNitzTimeSetTime = SystemClock.elapsedRealtime();
-            } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
-                mNitzZoneSetTime = SystemClock.elapsedRealtime();
             }
         }
     };
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 3c955eb..66b3adb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2192,6 +2192,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
+        if (StorageManager.isFileEncryptedNativeOnly()) {
+            // Not supported on FBE devices
+            return -1;
+        }
+
         if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
             password = "";
         } else if (TextUtils.isEmpty(password)) {
@@ -2268,6 +2273,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
+        if (StorageManager.isFileEncryptedNativeOnly()) {
+            // Not supported on FBE devices
+            return;
+        }
+
         try {
             mVold.fdeSetField(field, contents);
             return;
@@ -2287,6 +2297,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
+        if (StorageManager.isFileEncryptedNativeOnly()) {
+            // Not supported on FBE devices
+            return null;
+        }
+
         try {
             return mVold.fdeGetField(field);
         } catch (Exception e) {
@@ -2568,7 +2583,7 @@
     }
 
     @Override
-    public int mkdirs(String callingPkg, String appPath) {
+    public void mkdirs(String callingPkg, String appPath) {
         final int userId = UserHandle.getUserId(Binder.getCallingUid());
         final UserEnvironment userEnv = new UserEnvironment(userId);
 
@@ -2581,8 +2596,7 @@
         try {
             appFile = new File(appPath).getCanonicalFile();
         } catch (IOException e) {
-            Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
-            return -1;
+            throw new IllegalStateException("Failed to resolve " + appPath + ": " + e);
         }
 
         // Try translating the app path into a vold path, but require that it
@@ -2597,9 +2611,8 @@
 
             try {
                 mVold.mkdirs(appPath);
-                return 0;
             } catch (Exception e) {
-                Slog.wtf(TAG, e);
+                throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 3dbee42..b11b16e1 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -399,6 +399,16 @@
                 otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
             }
         } finally {
+            if (mHomeStack != null && !isTopStack(mHomeStack)) {
+                // Whenever split-screen is dismissed we want the home stack directly behind the
+                // currently top stack so it shows up when the top stack is finished.
+                final ActivityStack topStack = getTopStack();
+                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+                // once we have that.
+                mHomeStack.moveToFront("onSplitScreenModeDismissed");
+                topStack.moveToFront("onSplitScreenModeDismissed");
+            }
             mSupervisor.mWindowManager.continueSurfaceLayout();
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 083d306..fe992da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -246,6 +246,7 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.IBackupManager;
+import android.app.servertransaction.ConfigurationChangeItem;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
@@ -317,6 +318,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -631,6 +633,8 @@
 
     final ActivityStarter mActivityStarter;
 
+    final ClientLifecycleManager mLifecycleManager;
+
     final TaskChangeNotificationController mTaskChangeNotificationController;
 
     final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
@@ -713,6 +717,12 @@
 
     final UserController mUserController;
 
+    /**
+     * Packages that are being allowed to perform unrestricted app switches.  Mapping is
+     * User -> Type -> uid.
+     */
+    final SparseArray<ArrayMap<String, Integer>> mAllowAppSwitchUids = new SparseArray<>();
+
     final AppErrors mAppErrors;
 
     final AppWarnings mAppWarnings;
@@ -1772,6 +1782,11 @@
 
     final boolean mPermissionReviewRequired;
 
+    /**
+     * Whether to force background check on all apps (for battery saver) or not.
+     */
+    boolean mForceBackgroundCheck;
+
     private static String sTheRealBuildSerial = Build.UNKNOWN;
 
     /**
@@ -2687,6 +2702,7 @@
         mUserController = null;
         mVrController = null;
         mLockTaskController = null;
+        mLifecycleManager = null;
     }
 
     // Note: This method is invoked on the main thread but may need to attach various
@@ -2789,6 +2805,7 @@
         mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
+        mLifecycleManager = new ClientLifecycleManager();
 
         mProcessCpuThread = new Thread("CpuTracker") {
             @Override
@@ -2872,6 +2889,7 @@
 
     void onUserStoppedLocked(int userId) {
         mRecentTasks.unloadUserDataFromMemoryLocked(userId);
+        mAllowAppSwitchUids.remove(userId);
     }
 
     public void initPowerManagement() {
@@ -5121,7 +5139,8 @@
                     // because we don't support returning them across task boundaries. Also, to
                     // keep backwards compatibility we remove the task from recents when finishing
                     // task with root activity.
-                    res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
+                    res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+                            finishWithRootActivity, "finish-activity");
                     if (!res) {
                         Slog.i(TAG, "Removing task failed to finish activity");
                     }
@@ -8607,6 +8626,16 @@
         }
         switch (appop) {
             case AppOpsManager.MODE_ALLOWED:
+                // If force-background-check is enabled, restrict all apps that aren't whitelisted.
+                if (mForceBackgroundCheck &&
+                        UserHandle.isApp(uid) &&
+                        !isOnDeviceIdleWhitelistLocked(uid)) {
+                    if (DEBUG_BACKGROUND_CHECK) {
+                        Slog.i(TAG, "Force background check: " +
+                                uid + "/" + packageName + " restricted");
+                    }
+                    return ActivityManager.APP_START_MODE_DELAYED;
+                }
                 return ActivityManager.APP_START_MODE_NORMAL;
             case AppOpsManager.MODE_IGNORED:
                 return ActivityManager.APP_START_MODE_DELAYED;
@@ -8706,6 +8735,9 @@
         return ActivityManager.APP_START_MODE_NORMAL;
     }
 
+    /**
+     * @return whether a UID is in the system, user or temp doze whitelist.
+     */
     boolean isOnDeviceIdleWhitelistLocked(int uid) {
         final int appId = UserHandle.getAppId(uid);
         return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
@@ -10299,7 +10331,8 @@
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
+                return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS,
+                        "remove-task");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10489,12 +10522,12 @@
                             + " non-standard task " + taskId + " to windowing mode="
                             + windowingMode);
                 }
-                final ActivityDisplay display = task.getStack().getDisplay();
-                final ActivityStack stack = display.getOrCreateStack(windowingMode,
-                        task.getStack().getActivityType(), toTop);
-                // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
-                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
-                        "moveTaskToStack");
+
+                final ActivityStack stack = task.getStack();
+                if (toTop) {
+                    stack.moveToFront("setTaskWindowingMode", task);
+                }
+                stack.setWindowingMode(windowingMode);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -12611,6 +12644,18 @@
         }
     }
 
+    boolean checkAllowAppSwitchUid(int uid) {
+        ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid));
+        if (types != null) {
+            for (int i = types.size() - 1; i >= 0; i--) {
+                if (types.valueAt(i).intValue() == uid) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
             int callingPid, int callingUid, String name) {
         if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
@@ -12623,6 +12668,9 @@
         if (perm == PackageManager.PERMISSION_GRANTED) {
             return true;
         }
+        if (checkAllowAppSwitchUid(sourceUid)) {
+            return true;
+        }
 
         // If the actual IPC caller is different from the logical source, then
         // also see if they are allowed to control app switches.
@@ -12633,6 +12681,9 @@
             if (perm == PackageManager.PERMISSION_GRANTED) {
                 return true;
             }
+            if (checkAllowAppSwitchUid(callingUid)) {
+                return true;
+            }
         }
 
         Slog.w(TAG, name + " request from " + sourceUid + " stopped");
@@ -14144,6 +14195,16 @@
             readGrantedUriPermissionsLocked();
         }
 
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        if (pmi != null) {
+            pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,
+                    state -> updateForceBackgroundCheck(state.batterySaverEnabled));
+            updateForceBackgroundCheck(
+                    pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);
+        } else {
+            Slog.wtf(TAG, "PowerManagerInternal not found.");
+        }
+
         if (goingCallback != null) goingCallback.run();
         traceLog.traceBegin("ActivityManagerStartApps");
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
@@ -14242,6 +14303,23 @@
         }
     }
 
+    private void updateForceBackgroundCheck(boolean enabled) {
+        synchronized (this) {
+            if (mForceBackgroundCheck != enabled) {
+                mForceBackgroundCheck = enabled;
+
+                if (DEBUG_BACKGROUND_CHECK) {
+                    Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+                }
+
+                if (mForceBackgroundCheck) {
+                    // Stop background services for idle UIDs.
+                    doStopUidForIdleUidsLocked();
+                }
+            }
+        }
+    }
+
     void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
         synchronized (this) {
             mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -14909,6 +14987,7 @@
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
         String dumpPackage = null;
+        int dumpAppId = -1;
 
         int opti = 0;
         while (opti < args.length) {
@@ -15003,6 +15082,16 @@
             return;
         }
 
+        if (dumpPackage != null) {
+            try {
+                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+                        dumpPackage, 0);
+                dumpAppId = UserHandle.getAppId(info.uid);
+            } catch (NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+
         boolean more = false;
         // Is the caller requesting to dump a particular piece of data?
         if (opti < args.length) {
@@ -15110,7 +15199,7 @@
                             args.length - opti);
                 }
                 synchronized (this) {
-                    dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage);
+                    dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId);
                 }
             } else if ("oom".equals(cmd) || "o".equals(cmd)) {
                 synchronized (this) {
@@ -15296,7 +15385,7 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
             }
 
         } else {
@@ -15373,7 +15462,7 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -15528,22 +15617,12 @@
         }
     }
 
-    boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+    boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, SparseArray<UidRecord> uids,
             String header, boolean needSep) {
         boolean printed = false;
-        int whichAppId = -1;
-        if (dumpPackage != null) {
-            try {
-                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
-                        dumpPackage, 0);
-                whichAppId = UserHandle.getAppId(info.uid);
-            } catch (NameNotFoundException e) {
-                e.printStackTrace();
-            }
-        }
         for (int i=0; i<uids.size(); i++) {
             UidRecord uidRec = uids.valueAt(i);
-            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
                 continue;
             }
             if (!printed) {
@@ -15590,9 +15669,8 @@
     }
 
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
+            int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
         boolean needSep = false;
-        boolean printedAnything = false;
         int numPers = 0;
 
         pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
@@ -15610,7 +15688,6 @@
                     if (!needSep) {
                         pw.println("  All known processes:");
                         needSep = true;
-                        printedAnything = true;
                     }
                     pw.print(r.persistent ? "  *PERS*" : "  *APP*");
                         pw.print(" UID "); pw.print(procs.keyAt(ia));
@@ -15635,7 +15712,6 @@
                         pw.println();
                     }
                     pw.println("  Isolated process list (sorted by uid):");
-                    printedAnything = true;
                     printed = true;
                     needSep = true;
                 }
@@ -15657,7 +15733,6 @@
                         pw.println();
                     }
                     pw.println("  Active instrumentation:");
-                    printedAnything = true;
                     printed = true;
                     needSep = true;
                 }
@@ -15668,14 +15743,15 @@
         }
 
         if (mActiveUids.size() > 0) {
-            if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
-                printedAnything = needSep = true;
+            if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
+                needSep = true;
             }
         }
         if (dumpAll) {
             if (mValidateUids.size() > 0) {
-                if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
-                    printedAnything = needSep = true;
+                if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
+                        needSep)) {
+                    needSep = true;
                 }
             }
         }
@@ -15692,7 +15768,6 @@
                     pw.println("):");
             dumpProcessOomList(pw, this, mLruProcesses, "    ", "Proc", "PERS", false, dumpPackage);
             needSep = true;
-            printedAnything = true;
         }
 
         if (dumpAll || dumpPackage != null) {
@@ -15708,7 +15783,6 @@
                         needSep = true;
                         pw.println("  PID mappings:");
                         printed = true;
-                        printedAnything = true;
                     }
                     pw.print("    PID #"); pw.print(mPidsSelfLocked.keyAt(i));
                         pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
@@ -15731,7 +15805,6 @@
                         needSep = true;
                         pw.println("  Foreground Processes:");
                         printed = true;
-                        printedAnything = true;
                     }
                     pw.print("    PID #"); pw.print(mImportantProcesses.keyAt(i));
                             pw.print(": "); pw.println(mImportantProcesses.valueAt(i));
@@ -15742,7 +15815,6 @@
         if (mPersistentStartingProcesses.size() > 0) {
             if (needSep) pw.println();
             needSep = true;
-            printedAnything = true;
             pw.println("  Persisent processes that are starting:");
             dumpProcessList(pw, this, mPersistentStartingProcesses, "    ",
                     "Starting Norm", "Restarting PERS", dumpPackage);
@@ -15751,7 +15823,6 @@
         if (mRemovedProcesses.size() > 0) {
             if (needSep) pw.println();
             needSep = true;
-            printedAnything = true;
             pw.println("  Processes that are being removed:");
             dumpProcessList(pw, this, mRemovedProcesses, "    ",
                     "Removed Norm", "Removed PERS", dumpPackage);
@@ -15760,7 +15831,6 @@
         if (mProcessesOnHold.size() > 0) {
             if (needSep) pw.println();
             needSep = true;
-            printedAnything = true;
             pw.println("  Processes that are on old until the system is ready:");
             dumpProcessList(pw, this, mProcessesOnHold, "    ",
                     "OnHold Norm", "OnHold PERS", dumpPackage);
@@ -15769,9 +15839,6 @@
         needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
 
         needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
-        if (needSep) {
-            printedAnything = true;
-        }
 
         if (dumpPackage == null) {
             pw.println();
@@ -15978,6 +16045,32 @@
                 pw.println("  mNativeDebuggingApp=" + mNativeDebuggingApp);
             }
         }
+        if (mAllowAppSwitchUids.size() > 0) {
+            boolean printed = false;
+            for (int i = 0; i < mAllowAppSwitchUids.size(); i++) {
+                ArrayMap<String, Integer> types = mAllowAppSwitchUids.valueAt(i);
+                for (int j = 0; j < types.size(); j++) {
+                    if (dumpPackage == null ||
+                            UserHandle.getAppId(types.valueAt(j).intValue()) == dumpAppId) {
+                        if (needSep) {
+                            pw.println();
+                            needSep = false;
+                        }
+                        if (!printed) {
+                            pw.println("  mAllowAppSwitchUids:");
+                            printed = true;
+                        }
+                        pw.print("    User ");
+                        pw.print(mAllowAppSwitchUids.keyAt(i));
+                        pw.print(": Type ");
+                        pw.print(types.keyAt(j));
+                        pw.print(" = ");
+                        UserHandle.formatUid(pw, types.valueAt(j).intValue());
+                        pw.println();
+                    }
+                }
+            }
+        }
         if (dumpPackage == null) {
             if (mAlwaysFinishActivities) {
                 pw.println("  mAlwaysFinishActivities=" + mAlwaysFinishActivities);
@@ -16017,10 +16110,7 @@
                         pw.println();
             }
         }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
+        pw.println("  mForceBackgroundCheck=" + mForceBackgroundCheck);
     }
 
     boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -17956,7 +18046,7 @@
             PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
             String[] emptyArgs = new String[] { };
             catPw.println();
-            dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null);
+            dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1);
             catPw.println();
             mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
                     false, null).dumpLocked();
@@ -20509,9 +20599,11 @@
                 if (app.thread != null) {
                     if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
                             + app.processName + " new config " + configCopy);
-                    app.thread.scheduleConfigurationChanged(configCopy);
+                    mLifecycleManager.scheduleTransaction(app.thread,
+                            new ConfigurationChangeItem(configCopy));
                 }
             } catch (Exception e) {
+                Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
             }
         }
 
@@ -23279,6 +23371,24 @@
         }
     }
 
+    /**
+     * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
+     */
+    void doStopUidForIdleUidsLocked() {
+        final int size = mActiveUids.size();
+        for (int i = 0; i < size; i++) {
+            final int uid = mActiveUids.keyAt(i);
+            if (!UserHandle.isApp(uid)) {
+                continue;
+            }
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            if (!uidRec.idle) {
+                continue;
+            }
+            doStopUidLocked(uidRec.uid, uidRec);
+        }
+    }
+
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
         enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -24256,6 +24366,32 @@
                 }
             }
         }
+
+        @Override
+        public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
+            synchronized (ActivityManagerService.this) {
+                if (mUserController.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
+                    ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(userId);
+                    if (types == null) {
+                        if (uid < 0) {
+                            return;
+                        }
+                        types = new ArrayMap<>();
+                        mAllowAppSwitchUids.put(userId, types);
+                    }
+                    if (uid < 0) {
+                        types.remove(type);
+                    } else {
+                        types.put(type, uid);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public boolean isRuntimeRestarted() {
+            return mSystemServiceManager.isRuntimeRestarted();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 11590d6..a089e6c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -131,6 +131,12 @@
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.app.ResultInfo;
+import android.app.servertransaction.MoveToDisplayItem;
+import android.app.servertransaction.MultiWindowModeChangeItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PipModeChangeItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -611,8 +617,8 @@
                     "Reporting activity moved to display" + ", activityRecord=" + this
                             + ", displayId=" + displayId + ", config=" + config);
 
-            app.thread.scheduleActivityMovedToDisplay(appToken, displayId,
-                    new Configuration(config));
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new MoveToDisplayItem(displayId, config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -629,7 +635,8 @@
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
                     + config);
 
-            app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config));
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new ActivityConfigurationChangeItem(config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -640,8 +647,13 @@
             return;
         }
 
+        if (task.getStack().deferScheduleMultiWindowModeChanged()) {
+            // Don't do anything if we are currently deferring multi-window mode change.
+            return;
+        }
+
         // An activity is considered to be in multi-window mode if its task isn't fullscreen.
-        final boolean inMultiWindowMode = task.inMultiWindowMode();
+        final boolean inMultiWindowMode = inMultiWindowMode();
         if (inMultiWindowMode != mLastReportedMultiWindowMode) {
             mLastReportedMultiWindowMode = inMultiWindowMode;
             scheduleMultiWindowModeChanged(getConfiguration());
@@ -650,8 +662,9 @@
 
     private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
         try {
-            app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode,
-                    overrideConfig);
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new MultiWindowModeChangeItem(mLastReportedMultiWindowMode,
+                            overrideConfig));
         } catch (Exception e) {
             // If process died, I don't care.
         }
@@ -667,7 +680,7 @@
             // Picture-in-picture mode changes also trigger a multi-window mode change as well, so
             // update that here in order
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
-            mLastReportedMultiWindowMode = inPictureInPictureMode;
+            mLastReportedMultiWindowMode = inMultiWindowMode();
             final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
                     targetStackBounds, null);
             schedulePictureInPictureModeChanged(newConfig);
@@ -677,8 +690,9 @@
 
     private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
         try {
-            app.thread.schedulePictureInPictureModeChanged(appToken,
-                    mLastReportedPictureInPictureMode, overrideConfig);
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new PipModeChangeItem(mLastReportedPictureInPictureMode,
+                            overrideConfig));
         } catch (Exception e) {
             // If process died, no one cares.
         }
@@ -1365,8 +1379,8 @@
             try {
                 ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
                 ar.add(rintent);
-                app.thread.scheduleNewIntent(
-                        ar, appToken, state == PAUSED /* andPause */);
+                service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                        new NewIntentItem(ar, state == PAUSED));
                 unsent = false;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -1588,7 +1602,8 @@
             setVisible(true);
             sleeping = false;
             app.pendingUiClean = true;
-            app.thread.scheduleWindowVisibility(appToken, true /* showWindow */);
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new WindowVisibilityItem(true /* showWindow */));
             // The activity may be waiting for stop, but that is no longer appropriate for it.
             mStackSupervisor.mStoppingActivities.remove(this);
             mStackSupervisor.mGoingToSleepActivities.remove(this);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4816998..bdfd82f 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -104,6 +104,13 @@
 import android.app.ResultInfo;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.DestroyActivityItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -487,16 +494,18 @@
 
         // Need to make sure windowing mode is supported.
         int windowingMode = display.resolveWindowingMode(
-                null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+                null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());
         if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
             // Resolution to split-screen secondary for the primary split-screen stack means we want
             // to go fullscreen.
             windowingMode = WINDOWING_MODE_FULLSCREEN;
         }
 
+        final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack();
+
         // Take any required action due to us not supporting the preferred windowing mode.
         if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
-            if (display.hasSplitScreenPrimaryStack()
+            if (alreadyInSplitScreenMode
                     && (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                 // Looks like we can't launch in split screen mode, go ahead an dismiss split-screen
@@ -570,7 +579,7 @@
                 resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
             }
         } finally {
-            if (mDisplayId == DEFAULT_DISPLAY
+            if (!alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
                     && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // Make sure recents stack exist when creating a dock stack as it normally needs to
                 // be on the other side of the docked stack and we make visibility decisions based
@@ -1419,8 +1428,10 @@
                         prev.userId, System.identityHashCode(prev),
                         prev.shortComponentName);
                 mService.updateUsageStats(prev, false);
-                prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
-                        userLeaving, prev.configChangeFlags, pauseImmediately);
+
+                mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
+                        new PauseActivityItem(prev.finishing, userLeaving,
+                                prev.configChangeFlags, pauseImmediately));
             } catch (Exception e) {
                 // Ignore exception, if process died other code will cleanup.
                 Slog.w(TAG, "Exception thrown during pause", e);
@@ -1669,12 +1680,6 @@
         return true;
     }
 
-    /** Returns true if the stack is currently considered visible. */
-    boolean isVisible() {
-        return mWindowContainerController != null && mWindowContainerController.isVisible()
-                && !mForceHidden;
-    }
-
     boolean isTopStackOnDisplay() {
         return getDisplay().isTopStack(this);
     }
@@ -2055,7 +2060,8 @@
                     if (r.app != null && r.app.thread != null) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
                                 "Scheduling invisibility: " + r);
-                        r.app.thread.scheduleWindowVisibility(r.appToken, false);
+                        mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                                new WindowVisibilityItem(false /* showWindow */));
                     }
 
                     // Reset the flag indicating that an app can enter picture-in-picture once the
@@ -2575,13 +2581,15 @@
                         if (!next.finishing && N > 0) {
                             if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
                                     "Delivering results to " + next + ": " + a);
-                            next.app.thread.scheduleSendResult(next.appToken, a);
+                            mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+                                    next.appToken, new ActivityResultItem(a));
                         }
                     }
 
                     if (next.newIntents != null) {
-                        next.app.thread.scheduleNewIntent(
-                                next.newIntents, next.appToken, false /* andPause */);
+                        mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+                                next.appToken, new NewIntentItem(next.newIntents,
+                                        false /* andPause */));
                     }
 
                     // Well the app will no longer be stopped.
@@ -2598,8 +2606,9 @@
                     next.app.pendingUiClean = true;
                     next.app.forceProcessStateUpTo(mService.mTopProcessState);
                     next.clearOptionsLocked();
-                    next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
-                            mService.isNextTransitionForward(), resumeAnimOptions);
+                    mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken,
+                            new ResumeActivityItem(next.app.repProcState,
+                                    mService.isNextTransitionForward()));
 
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
                             + next);
@@ -3249,7 +3258,8 @@
                 ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
                 list.add(new ResultInfo(resultWho, requestCode,
                         resultCode, data));
-                r.app.thread.scheduleSendResult(r.appToken, list);
+                mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                        new ActivityResultItem(list));
                 return;
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending result to " + r, e);
@@ -3377,7 +3387,8 @@
                 }
                 EventLogTags.writeAmStopActivity(
                         r.userId, System.identityHashCode(r), r.shortComponentName);
-                r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
+                mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                        new StopActivityItem(r.visible, r.configChangeFlags));
                 if (shouldSleepOrShutDownActivities()) {
                     r.setSleeping(true);
                 }
@@ -4009,7 +4020,7 @@
                 // TODO: If the callers to removeTask() changes such that we have multiple places
                 //       where we are destroying the task, move this back into removeTask()
                 mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
-                        !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
+                        !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
             }
 
             // We must keep the task around until all activities are destroyed. The following
@@ -4174,8 +4185,8 @@
 
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
-                r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
-                        r.configChangeFlags);
+                mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                        new DestroyActivityItem(r.finishing, r.configChangeFlags));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process
                 // has crashed, our death notification will clean things
@@ -4988,13 +4999,22 @@
     TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             boolean toTop) {
+        return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop,
+                null /*activity*/, null /*source*/, null /*options*/);
+    }
+
+    TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+            boolean toTop, ActivityRecord activity, ActivityRecord source,
+            ActivityOptions options) {
         final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
                 voiceInteractor);
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, "createTaskRecord");
         final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController()
                 .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
-        if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
+        if (!mStackSupervisor.getLaunchingBoundsController()
+                .layoutTask(task, info.windowLayout, activity, source, options)
                 && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
             task.updateOverrideConfiguration(getOverrideBounds());
         }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5525cdb..445bf67 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -114,6 +114,7 @@
 import android.app.WaitResult;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.LaunchActivityItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -1392,7 +1393,8 @@
                 r.setLastReportedConfiguration(mergedConfiguration);
 
                 logIfTransactionTooLarge(r.intent, r.icicle);
-                app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
+                mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken,
+                        new LaunchActivityItem(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
@@ -1400,7 +1402,7 @@
                         mergedConfiguration.getOverrideConfiguration(), r.compat,
                         r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                         r.persistentState, results, newIntents, !andResume,
-                        mService.isNextTransitionForward(), profilerInfo);
+                        mService.isNextTransitionForward(), profilerInfo));
 
                 if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
                     // This may be a heavy-weight process!  Note that the package
@@ -2737,7 +2739,7 @@
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
-                        REMOVE_FROM_RECENTS);
+                        REMOVE_FROM_RECENTS, "remove-stack");
             }
         }
     }
@@ -2770,8 +2772,10 @@
     /**
      * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
      */
-    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
-        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+            String reason) {
+        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
+                reason);
     }
 
     /**
@@ -2785,10 +2789,10 @@
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
-            boolean pauseImmediately) {
+            boolean pauseImmediately, String reason) {
         final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
         if (tr != null) {
-            tr.removeTaskActivitiesLocked(pauseImmediately);
+            tr.removeTaskActivitiesLocked(pauseImmediately, reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             mService.mLockTaskController.clearLockedTask(tr);
             if (tr.isPersistable) {
@@ -2922,7 +2926,12 @@
 
     @Override
     public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
-        // TODO: Trim active task once b/68045330 is fixed
+        if (wasTrimmed) {
+            // Task was trimmed from the recent tasks list -- remove the active task record as well
+            // since the user won't really be able to go back to it
+            removeTaskByIdLocked(task.taskId, false /* killProcess */,
+                    false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+        }
         task.removedFromRecents();
     }
 
@@ -4299,6 +4308,10 @@
             final ActivityRecord r = task.mActivities.get(i);
             if (r.app != null && r.app.thread != null) {
                 mPipModeChangedActivities.add(r);
+                // If we are scheduling pip change, then remove this activity from multi-window
+                // change list as the processing of pip change will make sure multi-window changed
+                // message is processed in the right order relative to pip changed.
+                mMultiWindowModeChangedActivities.remove(r);
             }
         }
         mPipModeChangedTargetStackBounds = targetStackBounds;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 03162bb..2fc5dda 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -606,21 +606,9 @@
             return;
         }
 
-        if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) {
-            final ActivityStack homeStack = mSupervisor.mHomeStack;
-            final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
-            if (homeStackVisible) {
-                // We launch an activity while being in home stack, which means either launcher or
-                // recents into docked stack. We don't want the launched activity to be alone in a
-                // docked stack, so we want to immediately launch recents too.
-                if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
-                mService.mWindowManager.showRecentApps(true /* fromHome */);
-            }
-            return;
-        }
-
-        boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null);
+        final int clearTaskFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK;
+        boolean clearedTask = (mLaunchFlags & clearTaskFlags) == clearTaskFlags
+                && mReuseTask != null;
         if (startedActivityStack.inPinnedWindowingMode()
                 && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
                 || clearedTask)) {
@@ -1756,7 +1744,8 @@
                     mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
                     mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
-                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
+                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
+                    mOptions);
             addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
             updateBounds(mStartActivity.getTask(), mLaunchBounds);
 
@@ -1965,7 +1954,7 @@
         final ActivityRecord prev = mTargetStack.getTopActivity();
         final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
                 mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
-                mIntent, null, null, true);
+                mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
         addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
         mTargetStack.positionChildWindowContainerAtTop(task);
         if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
@@ -2122,18 +2111,13 @@
             return mReuseTask.getStack();
         }
 
-        final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
-                ? mPreferredDisplayId : INVALID_DISPLAY;
-        final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
-                vrDisplayId);
-
-        if (launchStack != null) {
-            return launchStack;
-        }
-
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
                  || mPreferredDisplayId != DEFAULT_DISPLAY) {
-            return null;
+            // We don't pass in the default display id into the get launch stack call so it can do a
+            // full resolution.
+            final int candidateDisplay =
+                    mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
+            return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
         }
         // Otherwise handle adjacent launch.
 
@@ -2165,7 +2149,7 @@
                         mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
                 if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
                     // There is a docked stack, but it isn't visible, so we can't launch into that.
-                    return null;
+                    return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
                 } else {
                     return dockedStack;
                 }
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index 17626ea..ab86dbdb 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -61,7 +61,7 @@
             try {
                 // We remove the task from recents to preserve backwards
                 if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
-                        REMOVE_FROM_RECENTS)) {
+                        REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
             } finally {
diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java
new file mode 100644
index 0000000..c04d103
--- /dev/null
+++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
+ * and execute them as a single transaction.
+ *
+ * @see ClientTransaction
+ */
+class ClientLifecycleManager {
+    // TODO(lifecycler): Implement building transactions or global transaction.
+    // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+    /**
+     * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request.
+     * @param transaction A sequence of client transaction items.
+     * @throws RemoteException
+     *
+     * @see ClientTransaction
+     */
+    void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+        transaction.schedule();
+    }
+
+    /**
+     * Schedule a single lifecycle request or callback to client activity.
+     * @param client Target client.
+     * @param activityToken Target activity token.
+     * @param stateRequest A request to move target activity to a desired lifecycle state.
+     * @throws RemoteException
+     *
+     * @see ClientTransactionItem
+     */
+    void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+            @NonNull ActivityLifecycleItem stateRequest) throws RemoteException {
+        final ClientTransaction clientTransaction = transactionWithState(client, activityToken,
+                stateRequest);
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
+     * Schedule a single callback delivery to client activity.
+     * @param client Target client.
+     * @param activityToken Target activity token.
+     * @param callback A request to deliver a callback.
+     * @throws RemoteException
+     *
+     * @see ClientTransactionItem
+     */
+    void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+            @NonNull ClientTransactionItem callback) throws RemoteException {
+        final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken,
+                callback);
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
+     * Schedule a single callback delivery to client application.
+     * @param client Target client.
+     * @param callback A request to deliver a callback.
+     * @throws RemoteException
+     *
+     * @see ClientTransactionItem
+     */
+    void scheduleTransaction(@NonNull IApplicationThread client,
+            @NonNull ClientTransactionItem callback) throws RemoteException {
+        final ClientTransaction clientTransaction = transactionWithCallback(client,
+                null /* activityToken */, callback);
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
+     * @return A new instance of {@link ClientTransaction} with a single lifecycle state request.
+     *
+     * @see ClientTransaction
+     * @see ClientTransactionItem
+     */
+    private static ClientTransaction transactionWithState(@NonNull IApplicationThread client,
+            @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) {
+        final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+        clientTransaction.setLifecycleStateRequest(stateRequest);
+        return clientTransaction;
+    }
+
+    /**
+     * @return A new instance of {@link ClientTransaction} with a single callback invocation.
+     *
+     * @see ClientTransaction
+     * @see ClientTransactionItem
+     */
+    private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,
+            IBinder activityToken, @NonNull ClientTransactionItem callback) {
+        final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+        clientTransaction.addCallback(callback);
+        return clientTransaction;
+    }
+}
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index 82019fd..65c4a42 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -58,10 +58,6 @@
     public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
     // Compatibility state: compatibility mode is enabled.
     public static final int COMPAT_FLAG_ENABLED = 1<<1;
-    // Unsupported zoom state: don't warn the user about unsupported zoom mode.
-    public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
-    // Unsupported compile SDK state: don't warn the user about unsupported compile SDK.
-    public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3;
 
     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
 
@@ -235,14 +231,6 @@
         return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
     }
 
-    public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
-        return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
-    }
-
-    public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) {
-        return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0;
-    }
-
     public void setFrontActivityAskCompatModeLocked(boolean ask) {
         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r != null) {
@@ -254,14 +242,6 @@
         setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
     }
 
-    public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
-        setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify);
-    }
-
-    public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) {
-        setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify);
-    }
-
     private void setPackageFlagLocked(String packageName, int flag, boolean set) {
         final int curFlags = getPackageFlags(packageName);
         final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java
index c762f7f..5aa7f58 100644
--- a/services/core/java/com/android/server/am/LaunchingBoundsController.java
+++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java
@@ -101,8 +101,12 @@
      * @return {@code true} if bounds were set on the task. {@code false} otherwise.
      */
     boolean layoutTask(TaskRecord task, WindowLayout layout) {
-        calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
-                mTmpRect);
+        return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
+    }
+
+    boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+            ActivityRecord source, ActivityOptions options) {
+        calculateBounds(task, layout, activity, source, options, mTmpRect);
 
         if (mTmpRect.isEmpty()) {
             return false;
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index e87b4e6..d77e1a2 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -250,7 +250,24 @@
     }
 
     /**
-     * @return whether the requested task is allowed to be launched.
+     * @return whether the requested task is allowed to be locked (either whitelisted, or declares
+     * lockTaskMode="always" in the manifest).
+     */
+    boolean isTaskWhitelisted(TaskRecord task) {
+        switch(task.mLockTaskAuth) {
+            case LOCK_TASK_AUTH_WHITELISTED:
+            case LOCK_TASK_AUTH_LAUNCHABLE:
+            case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
+                return true;
+            case LOCK_TASK_AUTH_PINNABLE:
+            case LOCK_TASK_AUTH_DONT_LOCK:
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * @return whether the requested task is disallowed to be launched.
      */
     boolean isLockTaskModeViolation(TaskRecord task) {
         return isLockTaskModeViolation(task, false);
@@ -258,7 +275,7 @@
 
     /**
      * @param isNewClearTask whether the task would be cleared as part of the operation.
-     * @return whether the requested task is allowed to be launched.
+     * @return whether the requested task is disallowed to be launched.
      */
     boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) {
         if (isLockTaskModeViolationInternal(task, isNewClearTask)) {
@@ -275,21 +292,18 @@
             // If the task is already at the top and won't be cleared, then allow the operation
             return false;
         }
-        final int lockTaskAuth = task.mLockTaskAuth;
-        switch (lockTaskAuth) {
-            case LOCK_TASK_AUTH_DONT_LOCK:
-                return !mLockTaskModeTasks.isEmpty();
-            case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
-            case LOCK_TASK_AUTH_LAUNCHABLE:
-            case LOCK_TASK_AUTH_WHITELISTED:
-                return false;
-            case LOCK_TASK_AUTH_PINNABLE:
-                // Pinnable tasks can't be launched on top of locktask tasks.
-                return !mLockTaskModeTasks.isEmpty();
-            default:
-                Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
-                return true;
+
+        // Allow recents activity if enabled by policy
+        if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) {
+            return false;
         }
+
+        return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty());
+    }
+
+    private boolean isRecentsAllowed(int userId) {
+        return (getLockTaskFeaturesForUser(userId)
+                & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0;
     }
 
     /**
@@ -491,6 +505,7 @@
         }
 
         if (mLockTaskModeTasks.isEmpty()) {
+            mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.userId);
             // Start lock task on the handler thread
             mHandler.post(() -> performStartLockTask(
                     task.intent.getComponent().getPackageName(),
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1a19601..6fb3dbb 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -40,7 +40,7 @@
 /**
  * Activity manager code dealing with processes.
  */
-final class ProcessList {
+public final class ProcessList {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
 
     // The minimum time we allow between crashes, for us to consider this
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 6e6d7e9..abb296e 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -80,6 +80,20 @@
 /**
  * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
  * least recent.
+ *
+ * The trimming logic can be boiled down to the following.  For recent task list with a number of
+ * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to
+ * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a
+ * sub-range are presented to the user, based on the device type, last task active time, or other
+ * task state. Tasks that are not in the visible range and are not returnable from the SystemUI
+ * (considering the back stack) are considered trimmable. If the device does not support recent
+ * tasks, then trimming is completely disabled.
+ *
+ * eg.
+ * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks
+ *     [VVV  VV   VVVV  V V V     ] // Visible tasks
+ *     [RRR  RR   XXXX  X X X     ] // Visible range tasks, eg. if the device only shows 5 tasks,
+ *                                  // 'X' tasks are trimmed.
  */
 class RecentTasks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
@@ -488,6 +502,18 @@
         }
     }
 
+    void onLockTaskModeStateChanged(int lockTaskModeState, int userId) {
+        if (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_LOCKED) {
+            return;
+        }
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+                remove(tr);
+            }
+        }
+    }
+
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final TaskRecord tr = mTasks.get(i);
@@ -496,7 +522,8 @@
             if (tr.userId != userId) return;
             if (!taskPackageName.equals(packageName)) return;
 
-            mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+            mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS,
+                    "remove-package-task");
         }
     }
 
@@ -513,7 +540,7 @@
                     && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
             if (sameComponent) {
                 mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
-                        REMOVE_FROM_RECENTS);
+                        REMOVE_FROM_RECENTS, "disabled-package");
             }
         }
     }
@@ -1001,12 +1028,13 @@
                     continue;
                 } else {
                     numVisibleTasks++;
-                    if (isInVisibleRange(task, numVisibleTasks)) {
+                    if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) {
                         // Keep visible tasks in range
                         i++;
                         continue;
                     } else {
-                        // Fall through to trim visible tasks that are no longer in range
+                        // Fall through to trim visible tasks that are no longer in range and
+                        // trimmable
                         if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
                                 "Trimming out-of-range visible task=" + task);
                     }
@@ -1122,6 +1150,28 @@
     }
 
     /**
+     * @return whether the given task can be trimmed even if it is outside the visible range.
+     */
+    protected boolean isTrimmable(TaskRecord task) {
+        final ActivityStack stack = task.getStack();
+        final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack;
+
+        // No stack for task, just trim it
+        if (stack == null) {
+            return true;
+        }
+
+        // Ignore tasks from different displays
+        if (stack.getDisplay() != homeStack.getDisplay()) {
+            return false;
+        }
+
+        // Trim tasks that are in stacks that are behind the home stack
+        final ActivityDisplay display = stack.getDisplay();
+        return display.getIndexOf(stack) < display.getIndexOf(homeStack);
+    }
+
+    /**
      * If needed, remove oldest existing entries in recents that are for the same kind
      * of task as the given one.
      */
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 365990f..83965ee 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1311,7 +1311,8 @@
      * Completely remove all activities associated with an existing
      * task starting at a specified index.
      */
-    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
+    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
+            String reason) {
         int numActivities = mActivities.size();
         for ( ; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
@@ -1325,7 +1326,7 @@
                 --activityNdx;
                 --numActivities;
             } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
-                    "clear-task-index", false, pauseImmediately)) {
+                    reason, false, pauseImmediately)) {
                 --activityNdx;
                 --numActivities;
             }
@@ -1337,7 +1338,7 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
+        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
         mReuseTask = false;
     }
 
@@ -1408,9 +1409,9 @@
         return null;
     }
 
-    void removeTaskActivitiesLocked(boolean pauseImmediately) {
+    void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
         // Just remove the entire task.
-        performClearTaskAtIndexLocked(0, pauseImmediately);
+        performClearTaskAtIndexLocked(0, pauseImmediately, reason);
     }
 
     String lockTaskAuthToString() {
@@ -1990,7 +1991,6 @@
         final Rect currentBounds = getOverrideBounds();
 
         mTmpConfig.setTo(getOverrideConfiguration());
-        final boolean oldMatchParentBounds = matchParentBounds();
         final Configuration newConfig = getOverrideConfiguration();
 
         final boolean matchParentBounds = bounds == null || bounds.isEmpty();
@@ -2013,12 +2013,17 @@
                     mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
         }
         onOverrideConfigurationChanged(newConfig);
+        return !mTmpConfig.equals(newConfig);
+    }
 
-        if (matchParentBounds != oldMatchParentBounds) {
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final boolean wasInMultiWindowMode = inMultiWindowMode();
+        super.onConfigurationChanged(newParentConfig);
+        if (wasInMultiWindowMode != inMultiWindowMode()) {
             mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
         }
-
-        return !mTmpConfig.equals(newConfig);
+        // TODO: Should also take care of Pip mode changes here.
     }
 
     /** Clears passed config and fills it with new override values. */
diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
index 600589a..b6f6ae6 100644
--- a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
+++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -39,7 +39,7 @@
         final PackageManager pm = context.getPackageManager();
         final CharSequence label = appInfo.loadSafeLabel(pm);
         final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
-                label, appInfo.compileSdkVersionCodename);
+                label);
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(context)
                 .setPositiveButton(R.string.ok, null)
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 90888f0..2c6fe94 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -34,6 +34,7 @@
 import android.os.BatteryManager;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -162,7 +163,10 @@
                 UserHandle.USER_CURRENT);
 
         mSensorListener = new SensorListener();
-        mInjector.registerSensorListener(mContext, mSensorListener);
+
+        if (mInjector.isInteractive(mContext)) {
+            mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler);
+        }
 
         mSettingsObserver = new SettingsObserver(mBgHandler);
         mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
@@ -170,6 +174,8 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_SHUTDOWN);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         mBroadcastReceiver = new Receiver();
         mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
 
@@ -584,6 +590,11 @@
                 if (level != -1 && scale != 0) {
                     batteryLevelChanged(level, scale);
                 }
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mInjector.unregisterSensorListener(mContext, mSensorListener);
+            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mInjector.registerSensorListener(mContext, mSensorListener,
+                        mInjector.getBackgroundHandler());
             }
         }
     }
@@ -591,11 +602,11 @@
     @VisibleForTesting
     static class Injector {
         public void registerSensorListener(Context context,
-                SensorEventListener sensorListener) {
+                SensorEventListener sensorListener, Handler handler) {
             SensorManager sensorManager = context.getSystemService(SensorManager.class);
             Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
             sensorManager.registerListener(sensorListener,
-                    lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+                    lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
         }
 
         public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
@@ -675,5 +686,9 @@
         public void cancelIdleJob(Context context) {
             BrightnessIdleJob.cancelJob(context);
         }
+
+        public boolean isInteractive(Context context) {
+            return context.getSystemService(PowerManager.class).isInteractive();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index f67bd04..fc4015d 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,19 +16,12 @@
 
 package com.android.server.job.controllers;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseBooleanArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.ForceAppStandbyTracker;
@@ -37,7 +30,6 @@
 import com.android.server.job.JobStore;
 
 import java.io.PrintWriter;
-import java.util.function.Predicate;
 
 public final class BackgroundJobsController extends StateController {
 
@@ -51,9 +43,6 @@
     private final JobSchedulerService mJobSchedulerService;
     private final IDeviceIdleController mDeviceIdleController;
 
-    private int[] mPowerWhitelistedUserAppIds;
-    private int[] mTempWhitelistedAppIds;
-
     private final ForceAppStandbyTracker mForceAppStandbyTracker;
 
 
@@ -67,28 +56,6 @@
         }
     }
 
-    private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                try {
-                    switch (intent.getAction()) {
-                        case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
-                            mPowerWhitelistedUserAppIds =
-                                    mDeviceIdleController.getAppIdUserWhitelist();
-                            break;
-                        case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
-                            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
-                            break;
-                    }
-                } catch (RemoteException rexc) {
-                    Slog.e(LOG_TAG, "Device idle controller not reachable");
-                }
-                updateAllJobRestrictionsLocked();
-            }
-        }
-    };
-
     private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
         super(service, context, lock);
         mJobSchedulerService = service;
@@ -97,19 +64,6 @@
 
         mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
 
-        try {
-            mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
-            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
-        } catch (RemoteException rexc) {
-            // Shouldn't happen as they are in the same process.
-            Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
-        }
-        IntentFilter powerWhitelistFilter = new IntentFilter();
-        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
-        context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
-                null, null);
-
         mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
         mForceAppStandbyTracker.start();
     }
@@ -128,31 +82,7 @@
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("BackgroundJobsController");
 
-        pw.print("Force all apps standby: ");
-        pw.println(mForceAppStandbyTracker.isForceAllAppsStandbyEnabled());
-
-        pw.print("Foreground uids: [");
-        final SparseBooleanArray foregroundUids = mForceAppStandbyTracker.getForegroudUids();
-
-        String sep = "";
-        for (int i = 0; i < foregroundUids.size(); i++) {
-            if (foregroundUids.valueAt(i)) {
-                pw.print(sep);
-                pw.print(UserHandle.formatUid(foregroundUids.keyAt(i)));
-                sep = " ";
-            }
-        }
-        pw.println("]");
-
-        pw.println("Restricted packages:");
-        for (Pair<Integer, String> uidAndPackage
-                : mForceAppStandbyTracker.getRestrictedUidPackages()) {
-            pw.print("  ");
-            pw.print(UserHandle.formatUid(uidAndPackage.first));
-            pw.print(" ");
-            pw.print(uidAndPackage.second);
-            pw.println();
-        }
+        mForceAppStandbyTracker.dump(pw, "");
 
         pw.println("Job state:");
         mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -165,15 +95,17 @@
             pw.print(" from ");
             UserHandle.formatUid(pw, uid);
             pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
-            if (isWhitelistedLocked(uid)) {
+            if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+                    mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
                 pw.print(", whitelisted");
             }
             pw.print(": ");
             pw.print(jobStatus.getSourcePackageName());
 
-            pw.print(" [background restrictions ");
+            pw.print(" [RUN_ANY_IN_BACKGROUND ");
             pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
-                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) ? "off]" : "on]");
+                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+                    ? "allowed]" : "disallowed]");
 
             if ((jobStatus.satisfiedConstraints
                     & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
@@ -218,19 +150,12 @@
         }
     }
 
-    private boolean isWhitelistedLocked(int uid) {
-        final int appId = UserHandle.getAppId(uid);
-        return ArrayUtils.contains(mTempWhitelistedAppIds, appId)
-                || ArrayUtils.contains(mPowerWhitelistedUserAppIds, appId);
-    }
-
     boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
 
         final int uid = jobStatus.getSourceUid();
         final String packageName = jobStatus.getSourcePackageName();
 
-        final boolean canRun = isWhitelistedLocked(uid)
-                || !mForceAppStandbyTracker.isRestricted(uid, packageName);
+        final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
 
         return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
     }
@@ -261,13 +186,18 @@
 
     private final Listener mForceAppStandbyListener = new Listener() {
         @Override
-        public void onRestrictionChanged(int uid, String packageName) {
+        public void updateAllJobs() {
+            updateAllJobRestrictionsLocked();
+        }
+
+        @Override
+        public void updateJobsForUid(int uid) {
             updateJobRestrictionsForUidLocked(uid);
         }
 
         @Override
-        public void onGlobalRestrictionChanged() {
-            updateAllJobRestrictionsLocked();
+        public void updateJobsForUidPackage(int uid, String packageName) {
+            updateJobRestrictionsForUidLocked(uid);
         }
     };
 }
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
new file mode 100644
index 0000000..41d9feb
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
+ * notification callbacks. This class implements the IContextHubClient object, and the implemented
+ * APIs must be thread-safe.
+ *
+ * @hide
+ */
+public class ContextHubClientBroker extends IContextHubClient.Stub
+        implements IBinder.DeathRecipient {
+    private static final String TAG = "ContextHubClientBroker";
+
+    /*
+     * The context of the service.
+     */
+    private final Context mContext;
+
+    /*
+     * The proxy to talk to the Context Hub HAL.
+     */
+    private final IContexthub mContextHubProxy;
+
+    /*
+     * The manager that registered this client.
+     */
+    private final ContextHubClientManager mClientManager;
+
+    /*
+     * The ID of the hub that this client is attached to.
+     */
+    private final int mAttachedContextHubId;
+
+    /*
+     * The host end point ID of this client.
+     */
+    private final short mHostEndPointId;
+
+    /*
+     * The remote callback interface for this client.
+     */
+    private final IContextHubClientCallback mCallbackInterface;
+
+    /*
+     * false if the connection has been closed by the client, true otherwise.
+     */
+    private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+
+    /* package */ ContextHubClientBroker(
+            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+            int contextHubId, short hostEndPointId, IContextHubClientCallback callback) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+        mClientManager = clientManager;
+        mAttachedContextHubId = contextHubId;
+        mHostEndPointId = hostEndPointId;
+        mCallbackInterface = callback;
+    }
+
+    /**
+     * Attaches a death recipient for this client
+     *
+     * @throws RemoteException if the client has already died
+     */
+    /* package */ void attachDeathRecipient() throws RemoteException {
+        mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+    }
+
+    /**
+     * Sends from this client to a nanoapp.
+     *
+     * @param message the message to send
+     * @return the error code of sending the message
+     */
+    @ContextHubTransaction.Result
+    @Override
+    public int sendMessageToNanoApp(NanoAppMessage message) {
+        ContextHubServiceUtil.checkPermissions(mContext);
+
+        int result;
+        if (mConnectionOpen.get()) {
+            ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
+                    mHostEndPointId, message);
+
+            try {
+                result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+                        + mAttachedContextHubId + ")", e);
+                result = Result.UNKNOWN_FAILURE;
+            }
+        } else {
+            Log.e(TAG, "Failed to send message to nanoapp: client connection is closed");
+            result = Result.UNKNOWN_FAILURE;
+        }
+
+        return ContextHubServiceUtil.toTransactionResult(result);
+    }
+
+    /**
+     * Closes the connection for this client with the service.
+     */
+    @Override
+    public void close() {
+        if (mConnectionOpen.getAndSet(false)) {
+            mClientManager.unregisterClient(mHostEndPointId);
+        }
+    }
+
+    /**
+     * Invoked when the underlying binder of this broker has died at the client process.
+     */
+    public void binderDied() {
+        try {
+            IContextHubClient.Stub.asInterface(this).close();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while closing client on death", e);
+        }
+    }
+
+    /**
+     * @return the ID of the context hub this client is attached to
+     */
+    /* package */ int getAttachedContextHubId() {
+        return mAttachedContextHubId;
+    }
+
+    /**
+     * @return the host endpoint ID of this client
+     */
+    /* package */ short getHostEndPointId() {
+        return mHostEndPointId;
+    }
+
+    /**
+     * Sends a message to the client associated with this object.
+     *
+     * @param message the message that came from a nanoapp
+     */
+    /* package */ void sendMessageToClient(NanoAppMessage message) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onMessageFromNanoApp(message);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+                        + mHostEndPointId + ")", e);
+            }
+        }
+    }
+
+    /**
+     * Handles a nanoapp load event.
+     *
+     * @param nanoAppId the ID of the nanoapp that was loaded.
+     */
+    /* package */ void onNanoAppLoaded(long nanoAppId) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onNanoAppLoaded(nanoAppId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client"
+                        + " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
+
+    /**
+     * Handles a nanoapp unload event.
+     *
+     * @param nanoAppId the ID of the nanoapp that was unloaded.
+     */
+    /* package */ void onNanoAppUnloaded(long nanoAppId) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onNanoAppUnloaded(nanoAppId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client"
+                        + " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
+
+    /**
+     * Handles a hub reset for this client.
+     */
+    /* package */ void onHubReset() {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onHubReset();
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onHubReset on client" +
+                        " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
new file mode 100644
index 0000000..d58a746
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubClientManager {
+    private static final String TAG = "ContextHubClientManager";
+
+    /*
+     * The maximum host endpoint ID value that a client can be assigned.
+     */
+    private static final int MAX_CLIENT_ID = 0x7fff;
+
+    /*
+     * Local flag to enable debug logging.
+     */
+    private static final boolean DEBUG_LOG_ENABLED = true;
+
+    /*
+     * The context of the service.
+     */
+    private final Context mContext;
+
+    /*
+     * The proxy to talk to the Context Hub.
+     */
+    private final IContexthub mContextHubProxy;
+
+    /*
+     * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
+     * A concurrent data structure is used since the registration/unregistration can occur in
+     * multiple threads.
+     */
+    private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap =
+            new ConcurrentHashMap<>();
+
+    /*
+     * The next host endpoint ID to start iterating for the next available host endpoint ID.
+     */
+    private int mNextHostEndpointId = 0;
+
+    /* package */ ContextHubClientManager(
+            Context context, IContexthub contextHubProxy) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+    }
+
+    /**
+     * Registers a new client with the service.
+     *
+     * @param clientCallback the callback interface of the client to register
+     * @param contextHubId   the ID of the hub this client is attached to
+     *
+     * @return the client interface
+     *
+     * @throws IllegalStateException if max number of clients have already registered
+     */
+    /* package */ IContextHubClient registerClient(
+            IContextHubClientCallback clientCallback, int contextHubId) {
+        ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId);
+
+        try {
+            broker.attachDeathRecipient();
+        } catch (RemoteException e) {
+            // The client process has died, so we close the connection and return null.
+            Log.e(TAG, "Failed to attach death recipient to client");
+            broker.close();
+            return null;
+        }
+
+        Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
+        return IContextHubClient.Stub.asInterface(broker);
+    }
+
+    /**
+     * Handles a message sent from a nanoapp.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp sent the message from
+     * @param message      the message send by a nanoapp
+     */
+    /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+        NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
+
+        if (DEBUG_LOG_ENABLED) {
+            String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast";
+            Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x"
+                    + Long.toHexString(clientMessage.getNanoAppId()));
+        }
+
+        if (clientMessage.isBroadcastMessage()) {
+            broadcastMessage(contextHubId, clientMessage);
+        } else {
+            ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
+            if (proxy != null) {
+                proxy.sendMessageToClient(clientMessage);
+            } else {
+                Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
+                        + message.hostEndPoint + ")");
+            }
+        }
+    }
+
+    /**
+     * Unregisters a client from the service.
+     *
+     * This method should be invoked as a result of a client calling the ContextHubClient.close(),
+     * or if the client process has died.
+     *
+     * @param hostEndPointId the host endpoint ID of the client that has died
+     */
+    /* package */ void unregisterClient(short hostEndPointId) {
+        if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) {
+            Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId);
+        } else {
+            Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID "
+                    + hostEndPointId);
+        }
+    }
+
+    /**
+     * Handles a nanoapp load event.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp was loaded.
+     * @param nanoAppId    the ID of the nanoapp that was loaded.
+     */
+    /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
+        forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
+    }
+
+    /**
+     * Handles a nanoapp unload event.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp was unloaded.
+     * @param nanoAppId    the ID of the nanoapp that was unloaded.
+     */
+    /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
+        forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
+    }
+
+    /**
+     * Handles a hub reset.
+     *
+     * @param contextHubId the ID of the hub that has reset.
+     */
+    /* package */ void onHubReset(int contextHubId) {
+        forEachClientOfHub(contextHubId, client -> client.onHubReset());
+    }
+
+    /**
+     * Creates a new ContextHubClientBroker object for a client and registers it with the client
+     * manager.
+     *
+     * @param clientCallback the callback interface of the client to register
+     * @param contextHubId   the ID of the hub this client is attached to
+     *
+     * @return the ContextHubClientBroker object
+     *
+     * @throws IllegalStateException if max number of clients have already registered
+     */
+    private synchronized ContextHubClientBroker createNewClientBroker(
+            IContextHubClientCallback clientCallback, int contextHubId) {
+        if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
+            throw new IllegalStateException("Could not register client - max limit exceeded");
+        }
+
+        ContextHubClientBroker broker = null;
+        int id = mNextHostEndpointId;
+        for (int i = 0; i <= MAX_CLIENT_ID; i++) {
+            if (!mHostEndPointIdToClientMap.containsKey(id)) {
+                broker = new ContextHubClientBroker(
+                        mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback);
+                mHostEndPointIdToClientMap.put((short)id, broker);
+                mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+                break;
+            }
+
+            id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+        }
+
+        return broker;
+    }
+
+    /**
+     * Broadcasts a message from a nanoapp to all clients attached to the associated hub.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp sent the message from
+     * @param message      the message send by a nanoapp
+     */
+    private void broadcastMessage(int contextHubId, NanoAppMessage message) {
+        forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
+    }
+
+    /**
+     * Runs a command for each client that is attached to a hub with the given ID.
+     *
+     * @param contextHubId the ID of the hub
+     * @param callback     the command to invoke for the client
+     */
+    private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
+        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+            if (broker.getAttachedContextHubId() == contextHubId) {
+                callback.accept(broker);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index da481a8..e08c659 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -16,12 +16,10 @@
 
 package com.android.server.location;
 
-import android.Manifest;
 import android.content.Context;
 import android.hardware.contexthub.V1_0.AsyncEventType;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.HostEndPoint;
 import android.hardware.contexthub.V1_0.HubAppInfo;
 import android.hardware.contexthub.V1_0.IContexthub;
 import android.hardware.contexthub.V1_0.IContexthubCallback;
@@ -29,13 +27,17 @@
 import android.hardware.contexthub.V1_0.TransactionResult;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
+import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubCallback;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
 import android.hardware.location.IContextHubService;
 import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoApp;
 import android.hardware.location.NanoAppBinary;
 import android.hardware.location.NanoAppFilter;
 import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -49,7 +51,9 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -58,9 +62,6 @@
  */
 public class ContextHubService extends IContextHubService.Stub {
     private static final String TAG = "ContextHubService";
-    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
-    private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
-            + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
 
     /*
      * Constants for the type of transaction that is defined by ContextHubService.
@@ -104,6 +105,12 @@
     // The manager for transaction queue
     private final ContextHubTransactionManager mTransactionManager;
 
+    // The manager for sending messages to/from clients
+    private final ContextHubClientManager mClientManager;
+
+    // The default client for old API clients
+    private final Map<Integer, IContextHubClient> mDefaultClientMap;
+
     /**
      * Class extending the callback to register with a Context Hub.
      */
@@ -146,21 +153,34 @@
         mContextHubProxy = getContextHubProxy();
         if (mContextHubProxy == null) {
             mTransactionManager = null;
+            mClientManager = null;
+            mDefaultClientMap = Collections.EMPTY_MAP;
             mContextHubInfo = new ContextHubInfo[0];
             return;
         }
 
-        mTransactionManager = new ContextHubTransactionManager(mContextHubProxy);
+        mClientManager = new ContextHubClientManager(mContext, mContextHubProxy);
+        mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager);
 
         List<ContextHub> hubList;
         try {
             hubList = mContextHubProxy.getHubs();
         } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException while getting Context Hub info");
+            Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubList = Collections.emptyList();
         }
         mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
 
+        HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
+        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+            int contextHubId = contextHubInfo.getId();
+
+            IContextHubClient client = mClientManager.registerClient(
+                    createDefaultClientCallback(contextHubId), contextHubId);
+            defaultClientMap.put(contextHubId, client);
+        }
+        mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
+
         for (ContextHubInfo contextHubInfo : mContextHubInfo) {
             int contextHubId = contextHubInfo.getId();
             try {
@@ -168,7 +188,7 @@
                         contextHubId, new ContextHubServiceCallback(contextHubId));
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
-                        + contextHubId + ")");
+                        + contextHubId + ")", e);
             }
         }
 
@@ -185,6 +205,53 @@
     }
 
     /**
+     * Creates a default client callback for old API clients.
+     *
+     * @param contextHubId the ID of the hub to attach this client to
+     * @return the internal callback interface
+     */
+    private IContextHubClientCallback createDefaultClientCallback(int contextHubId) {
+        return new IContextHubClientCallback.Stub() {
+            @Override
+            public void onMessageFromNanoApp(NanoAppMessage message) {
+                int nanoAppInstanceId =
+                        mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ?
+                        mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1;
+
+                onMessageReceiptOldApi(
+                        message.getMessageType(), contextHubId, nanoAppInstanceId,
+                        message.getMessageBody());
+            }
+
+            @Override
+            public void onHubReset() {
+                byte[] data = {TransactionResult.SUCCESS};
+                onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+            }
+
+            @Override
+            public void onNanoAppAborted(long nanoAppId, int abortCode) {
+            }
+
+            @Override
+            public void onNanoAppLoaded(long nanoAppId) {
+            }
+
+            @Override
+            public void onNanoAppUnloaded(long nanoAppId) {
+            }
+
+            @Override
+            public void onNanoAppEnabled(long nanoAppId) {
+            }
+
+            @Override
+            public void onNanoAppDisabled(long nanoAppId) {
+            }
+        };
+    }
+
+    /**
      * @return the IContexthub proxy interface
      */
     private IContexthub getContextHubProxy() {
@@ -192,7 +259,7 @@
         try {
             proxy = IContexthub.getService(true /* retry */);
         } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy");
+            Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
         } catch (NoSuchElementException e) {
             Log.i(TAG, "Context Hub HAL service not found");
         }
@@ -204,6 +271,7 @@
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
         checkPermissions();
         mCallbacksList.register(callback);
+
         Log.d(TAG, "Added callback, total callbacks " +
                 mCallbacksList.getRegisteredCallbackCount());
         return 0;
@@ -292,7 +360,7 @@
             @Override
             public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
                 byte[] data = {(byte) result};
-                onMessageReceipt(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
+                onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
             }
         };
     }
@@ -449,41 +517,37 @@
             return -1;
         }
         if (msg.getData() == null) {
-            Log.w(TAG, "ContextHubMessage message body cannot be null");
+            Log.e(TAG, "ContextHubMessage message body cannot be null");
+            return -1;
+        }
+        if (!mDefaultClientMap.containsKey(hubHandle)) {
+            Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
             return -1;
         }
 
-        int result;
+        boolean success = false;
         if (nanoAppHandle == OS_APP_INSTANCE) {
             if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
-                result = queryNanoAppsInternal(hubHandle);
+                success = (queryNanoAppsInternal(hubHandle) == Result.OK);
             } else {
                 Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
-                result = Result.BAD_PARAMS;
             }
         } else {
             NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle);
             if (info != null) {
-                ContextHubMsg hubMessage = new ContextHubMsg();
-                hubMessage.appName = info.getAppId();
-                hubMessage.msgType = msg.getMsgType();
-                hubMessage.hostEndPoint = HostEndPoint.UNSPECIFIED;
-                ContextHubServiceUtil.copyToByteArrayList(msg.getData(), hubMessage.msg);
+                NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
+                        info.getAppId(), msg.getMsgType(), msg.getData());
 
-                try {
-                    result = mContextHubProxy.sendMessageToHub(hubHandle, hubMessage);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to send nanoapp message - RemoteException");
-                    result = Result.UNKNOWN_FAILURE;
-                }
+                IContextHubClient client = mDefaultClientMap.get(hubHandle);
+                success = (client.sendMessageToNanoApp(message) ==
+                        ContextHubTransaction.TRANSACTION_SUCCESS);
             } else {
                 Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID "
                         + nanoAppHandle + " does not exist.");
-                result = Result.BAD_PARAMS;
             }
         }
 
-        return (result == Result.OK ? 0 : -1);
+        return success ? 0 : -1;
     }
 
     /**
@@ -493,12 +557,7 @@
      * @param message      the message contents
      */
     private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
-        // TODO(b/67734082): Send to new API clients
-        byte[] data = ContextHubServiceUtil.createPrimitiveByteArray(message.msg);
-
-        int nanoAppInstanceId = mNanoAppIdToInstanceMap.containsKey(message.appName) ?
-                mNanoAppIdToInstanceMap.get(message.appName) : -1;
-        onMessageReceipt(message.msgType, contextHubId, nanoAppInstanceId, data);
+        mClientManager.onMessageFromNanoApp(contextHubId, message);
     }
 
     /**
@@ -534,7 +593,7 @@
         data[0] = (byte) result;
         ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId);
 
-        onMessageReceipt(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+        onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
     }
 
     /**
@@ -552,7 +611,7 @@
 
         byte[] data = new byte[1];
         data[0] = (byte) result;
-        onMessageReceipt(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+        onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
     }
 
     /**
@@ -577,8 +636,7 @@
             mTransactionManager.onHubReset();
             queryNanoAppsInternal(contextHubId);
 
-            byte[] data = {TransactionResult.SUCCESS};
-            onMessageReceipt(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+            mClientManager.onHubReset(contextHubId);
         } else {
             Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = "
                     + eventType + ")");
@@ -641,6 +699,45 @@
         }
     }
 
+    /**
+     * @param contextHubId the hub ID to validate
+     * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
+     */
+    private boolean isValidContextHubId(int contextHubId) {
+        for (ContextHubInfo hubInfo : mContextHubInfo) {
+            if (hubInfo.getId() == contextHubId) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Creates and registers a client at the service for the specified Context Hub.
+     *
+     * @param clientCallback the client interface to register with the service
+     * @param contextHubId   the ID of the hub this client is attached to
+     * @return the generated client interface, null if registration was unsuccessful
+     *
+     * @throws IllegalArgumentException if contextHubId is not a valid ID
+     * @throws IllegalStateException if max number of clients have already registered
+     * @throws NullPointerException if clientCallback is null
+     */
+    @Override
+    public IContextHubClient createClient(
+            IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException {
+        checkPermissions();
+        if (!isValidContextHubId(contextHubId)) {
+            throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
+        }
+        if (clientCallback == null) {
+            throw new NullPointerException("Cannot register client with null callback");
+        }
+
+        return mClientManager.registerClient(clientCallback, contextHubId);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -664,10 +761,10 @@
     }
 
     private void checkPermissions() {
-        mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+        ContextHubServiceUtil.checkPermissions(mContext);
     }
 
-    private int onMessageReceipt(int msgType, int hubHandle, int appInstance, byte[] data) {
+    private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
         if (data == null) {
             return -1;
         }
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index ddbaf86..6faeb72 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -16,11 +16,15 @@
 
 package com.android.server.location;
 
+import android.Manifest;
+import android.content.Context;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
 import android.hardware.contexthub.V1_0.HostEndPoint;
 import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.Result;
 import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppBinary;
 import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
@@ -34,6 +38,9 @@
  */
 /* package */ class ContextHubServiceUtil {
     private static final String TAG = "ContextHubServiceUtil";
+    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+    private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+            + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
 
     /**
      * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
@@ -165,4 +172,40 @@
                 message.appName, message.msgType, messageArray,
                 message.hostEndPoint == HostEndPoint.BROADCAST);
     }
+
+    /**
+     * Checks for location hardware permissions.
+     *
+     * @param context the context of the service
+     */
+    /* package */
+    static void checkPermissions(Context context) {
+        context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+    }
+
+    /**
+     * Helper function to convert from the HAL Result enum error code to the
+     * ContextHubTransaction.Result type.
+     *
+     * @param halResult the Result enum error code
+     * @return the ContextHubTransaction.Result equivalent
+     */
+    @ContextHubTransaction.Result
+    /* package */
+    static int toTransactionResult(int halResult) {
+        switch (halResult) {
+            case Result.OK:
+                return ContextHubTransaction.TRANSACTION_SUCCESS;
+            case Result.BAD_PARAMS:
+                return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+            case Result.NOT_INIT:
+                return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+            case Result.TRANSACTION_PENDING:
+                return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+            case Result.TRANSACTION_FAILED:
+            case Result.UNKNOWN_FAILURE:
+            default: /* fall through */
+                return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
index 898b76c..47d9d56 100644
--- a/services/core/java/com/android/server/location/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
@@ -58,6 +58,11 @@
     private final IContexthub mContextHubProxy;
 
     /*
+     * The manager for all clients for the service.
+     */
+    private final ContextHubClientManager mClientManager;
+
+    /*
      * A queue containing the current transactions
      */
     private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
@@ -73,8 +78,10 @@
     private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
     private ScheduledFuture<?> mTimeoutFuture = null;
 
-    /* package */ ContextHubTransactionManager(IContexthub contextHubProxy) {
+    /* package */ ContextHubTransactionManager(
+            IContexthub contextHubProxy, ContextHubClientManager clientManager) {
         mContextHubProxy = contextHubProxy;
+        mClientManager = clientManager;
     }
 
     /**
@@ -113,6 +120,9 @@
             /* package */ void onTransactionComplete(int result) {
                 try {
                     onCompleteCallback.onTransactionComplete(result);
+                    if (result == Result.OK) {
+                        mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
+                    }
                 } catch (RemoteException e) {
                     Log.e(TAG, "RemoteException while calling client onTransactionComplete");
                 }
@@ -153,6 +163,9 @@
             /* package */ void onTransactionComplete(int result) {
                 try {
                     onCompleteCallback.onTransactionComplete(result);
+                    if (result == Result.OK) {
+                        mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
+                    }
                 } catch (RemoteException e) {
                     Log.e(TAG, "RemoteException while calling client onTransactionComplete");
                 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
new file mode 100644
index 0000000..2bd9cab
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+
+import android.app.ActivityManager;
+import android.net.NetworkPolicyManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.ProcessList;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+public class NetworkPolicyLogger {
+    static final String TAG = "NetworkPolicy";
+
+    static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+    static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final int MAX_LOG_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 20 : 50;
+    private static final int MAX_NETWORK_BLOCKED_LOG_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 50 : 100;
+
+    private static final int EVENT_TYPE_GENERIC = 0;
+    private static final int EVENT_NETWORK_BLOCKED = 1;
+    private static final int EVENT_UID_STATE_CHANGED = 2;
+    private static final int EVENT_POLICIES_CHANGED = 3;
+    private static final int EVENT_METEREDNESS_CHANGED = 4;
+    private static final int EVENT_USER_STATE_REMOVED = 5;
+    private static final int EVENT_RESTRICT_BG_CHANGED = 6;
+    private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7;
+    private static final int EVENT_APP_IDLE_STATE_CHANGED = 8;
+    private static final int EVENT_PAROLE_STATE_CHANGED = 9;
+    private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
+    private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
+    private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+
+    static final int NTWK_BLOCKED_POWER = 0;
+    static final int NTWK_ALLOWED_NON_METERED = 1;
+    static final int NTWK_BLOCKED_BLACKLIST = 2;
+    static final int NTWK_ALLOWED_WHITELIST = 3;
+    static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+    static final int NTWK_BLOCKED_BG_RESTRICT = 5;
+    static final int NTWK_ALLOWED_DEFAULT = 6;
+
+    private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
+    private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
+    private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
+
+    private final Object mLock = new Object();
+
+    void networkBlocked(int uid, int reason) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+            mNetworkBlockedBuffer.networkBlocked(uid, reason);
+        }
+    }
+
+    void uidStateChanged(int uid, int procState, long procStateSeq) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG,
+                    uid + " state changed to " + procState + " with seq=" + procStateSeq);
+            mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq);
+        }
+    }
+
+    void event(String msg) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, msg);
+            mEventsBuffer.event(msg);
+        }
+    }
+
+    void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy));
+            mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy);
+        }
+    }
+
+    void meterednessChanged(int netId, boolean newMetered) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered));
+            mEventsBuffer.meterednessChanged(netId, newMetered);
+        }
+    }
+
+    void removingUserState(int userId) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getUserRemovedLog(userId));
+            mEventsBuffer.userRemoved(userId);
+        }
+    }
+
+    void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG,
+                    getRestrictBackgroundChangedLog(oldValue, newValue));
+            mEventsBuffer.restrictBackgroundChanged(oldValue, newValue);
+        }
+    }
+
+    void deviceIdleModeEnabled(boolean enabled) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled));
+            mEventsBuffer.deviceIdleModeEnabled(enabled);
+        }
+    }
+
+    void appIdleStateChanged(int uid, boolean idle) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle));
+            mEventsBuffer.appIdleStateChanged(uid, idle);
+        }
+    }
+
+    void paroleStateChanged(boolean paroleOn) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
+            mEventsBuffer.paroleStateChanged(paroleOn);
+        }
+    }
+
+    void tempPowerSaveWlChanged(int appId, boolean added) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added));
+            mEventsBuffer.tempPowerSaveWlChanged(appId, added);
+        }
+    }
+
+    void uidFirewallRuleChanged(int chain, int uid, int rule) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule));
+            mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule);
+        }
+    }
+
+    void firewallChainEnabled(int chain, boolean enabled) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled));
+            mEventsBuffer.firewallChainEnabled(chain, enabled);
+        }
+    }
+
+    void firewallRulesChanged(int chain, int[] uids, int[] rules) {
+        synchronized (mLock) {
+            final String log = "Firewall rules changed for " + getFirewallChainName(chain)
+                    + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules);
+            if (LOGD) Slog.d(TAG, log);
+            mEventsBuffer.event(log);
+        }
+    }
+
+    void dumpLogs(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("mEventLogs (most recent first):");
+            pw.increaseIndent();
+            mEventsBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mNetworkBlockedLogs (most recent first):");
+            pw.increaseIndent();
+            mNetworkBlockedBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mUidStateChangeLogs (most recent first):");
+            pw.increaseIndent();
+            mUidStateChangeBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+        }
+    }
+
+    private static String getBlockedReason(int reason) {
+        switch (reason) {
+            case NTWK_BLOCKED_POWER:
+                return "blocked by power restrictions";
+            case NTWK_ALLOWED_NON_METERED:
+                return "allowed on unmetered network";
+            case NTWK_BLOCKED_BLACKLIST:
+                return "blacklisted on metered network";
+            case NTWK_ALLOWED_WHITELIST:
+                return "whitelisted on metered network";
+            case NTWK_ALLOWED_TMP_WHITELIST:
+                return "temporary whitelisted on metered network";
+            case NTWK_BLOCKED_BG_RESTRICT:
+                return "blocked when background is restricted";
+            case NTWK_ALLOWED_DEFAULT:
+                return "allowed by default";
+            default:
+                return String.valueOf(reason);
+        }
+    }
+
+    private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
+        return "Policy for " + uid + " changed from "
+                + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
+                + NetworkPolicyManager.uidPoliciesToString(newPolicy);
+    }
+
+    private static String getMeterednessChangedLog(int netId, boolean newMetered) {
+        return "Meteredness of netId=" + netId + " changed to " + newMetered;
+    }
+
+    private static String getUserRemovedLog(int userId) {
+        return "Remove state for u" + userId;
+    }
+
+    private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) {
+        return "Changed restrictBackground: " + oldValue + "->" + newValue;
+    }
+
+    private static String getDeviceIdleModeEnabled(boolean enabled) {
+        return "DeviceIdleMode enabled: " + enabled;
+    }
+
+    private static String getAppIdleChangedLog(int uid, boolean idle) {
+        return "App idle state of uid " + uid + ": " + idle;
+    }
+
+    private static String getParoleStateChanged(boolean paroleOn) {
+        return "Parole state: " + paroleOn;
+    }
+
+    private static String getTempPowerSaveWlChangedLog(int appId, boolean added) {
+        return "temp-power-save whitelist for " + appId + " changed to: " + added;
+    }
+
+    private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) {
+        return String.format("Firewall rule changed: %d-%s-%s",
+                uid, getFirewallChainName(chain), getFirewallRuleName(rule));
+    }
+
+    private static String getFirewallChainEnabledLog(int chain, boolean enabled) {
+        return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
+    }
+
+    private static String getFirewallChainName(int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+                return FIREWALL_CHAIN_NAME_DOZABLE;
+            case FIREWALL_CHAIN_STANDBY:
+                return FIREWALL_CHAIN_NAME_STANDBY;
+            case FIREWALL_CHAIN_POWERSAVE:
+                return FIREWALL_CHAIN_NAME_POWERSAVE;
+            default:
+                return String.valueOf(chain);
+        }
+    }
+
+    private static String getFirewallRuleName(int rule) {
+        switch (rule) {
+            case FIREWALL_RULE_DEFAULT:
+                return "default";
+            case FIREWALL_RULE_ALLOW:
+                return "allow";
+            case FIREWALL_RULE_DENY:
+                return "deny";
+            default:
+                return String.valueOf(rule);
+        }
+    }
+
+    private final static class LogBuffer extends RingBuffer<Data> {
+        private static final SimpleDateFormat sFormatter
+                = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS");
+        private static final Date sDate = new Date();
+
+        public LogBuffer(int capacity) {
+            super(Data.class, capacity);
+        }
+
+        public void uidStateChanged(int uid, int procState, long procStateSeq) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_UID_STATE_CHANGED;
+            data.ifield1 = uid;
+            data.ifield2 = procState;
+            data.lfield1 = procStateSeq;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void event(String msg) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_TYPE_GENERIC;
+            data.sfield1 = msg;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void networkBlocked(int uid, int reason) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_NETWORK_BLOCKED;
+            data.ifield1 = uid;
+            data.ifield2 = reason;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_POLICIES_CHANGED;
+            data.ifield1 = uid;
+            data.ifield2 = oldPolicy;
+            data.ifield3 = newPolicy;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void meterednessChanged(int netId, boolean newMetered) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_METEREDNESS_CHANGED;
+            data.ifield1 = netId;
+            data.bfield1 = newMetered;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void userRemoved(int userId) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_USER_STATE_REMOVED;
+            data.ifield1 = userId;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_RESTRICT_BG_CHANGED;
+            data.bfield1 = oldValue;
+            data.bfield2 = newValue;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void deviceIdleModeEnabled(boolean enabled) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_DEVICE_IDLE_MODE_ENABLED;
+            data.bfield1 = enabled;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void appIdleStateChanged(int uid, boolean idle) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_APP_IDLE_STATE_CHANGED;
+            data.ifield1 = uid;
+            data.bfield1 = idle;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void paroleStateChanged(boolean paroleOn) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_PAROLE_STATE_CHANGED;
+            data.bfield1 = paroleOn;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void tempPowerSaveWlChanged(int appId, boolean added) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED;
+            data.ifield1 = appId;
+            data.bfield1 = added;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void uidFirewallRuleChanged(int chain, int uid, int rule) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_UID_FIREWALL_RULE_CHANGED;
+            data.ifield1 = chain;
+            data.ifield2 = uid;
+            data.ifield3 = rule;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void firewallChainEnabled(int chain, boolean enabled) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_FIREWALL_CHAIN_ENABLED;
+            data.ifield1 = chain;
+            data.bfield1 = enabled;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void reverseDump(IndentingPrintWriter pw) {
+            final Data[] allData = toArray();
+            for (int i = allData.length - 1; i >= 0; --i) {
+                if (allData[i] == null) {
+                    pw.println("NULL");
+                    continue;
+                }
+                pw.print(formatDate(allData[i].timeStamp));
+                pw.print(" - ");
+                pw.println(getContent(allData[i]));
+            }
+        }
+
+        public String getContent(Data data) {
+            switch (data.type) {
+                case EVENT_TYPE_GENERIC:
+                    return data.sfield1;
+                case EVENT_NETWORK_BLOCKED:
+                    return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+                case EVENT_UID_STATE_CHANGED:
+                    return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2)
+                            + "-" + data.lfield1;
+                case EVENT_POLICIES_CHANGED:
+                    return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3);
+                case EVENT_METEREDNESS_CHANGED:
+                    return getMeterednessChangedLog(data.ifield1, data.bfield1);
+                case EVENT_USER_STATE_REMOVED:
+                    return getUserRemovedLog(data.ifield1);
+                case EVENT_RESTRICT_BG_CHANGED:
+                    return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2);
+                case EVENT_DEVICE_IDLE_MODE_ENABLED:
+                    return getDeviceIdleModeEnabled(data.bfield1);
+                case EVENT_APP_IDLE_STATE_CHANGED:
+                    return getAppIdleChangedLog(data.ifield1, data.bfield1);
+                case EVENT_PAROLE_STATE_CHANGED:
+                    return getParoleStateChanged(data.bfield1);
+                case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+                    return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1);
+                case EVENT_UID_FIREWALL_RULE_CHANGED:
+                    return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
+                case EVENT_FIREWALL_CHAIN_ENABLED:
+                    return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+                default:
+                    return String.valueOf(data.type);
+            }
+        }
+
+        private String formatDate(long millis) {
+            sDate.setTime(millis);
+            return sFormatter.format(sDate);
+        }
+    }
+
+    public final static class Data {
+        int type;
+        long timeStamp;
+
+        int ifield1;
+        int ifield2;
+        int ifield3;
+        long lfield1;
+        boolean bfield1;
+        boolean bfield2;
+        String sfield1;
+
+        public void reset(){
+            sfield1 = null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 3fa3cd4..fdfe241 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -82,6 +82,13 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -248,9 +255,9 @@
  * </ul>
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
-    static final String TAG = "NetworkPolicy";
-    private static final boolean LOGD = false;
-    private static final boolean LOGV = false;
+    static final String TAG = NetworkPolicyLogger.TAG;
+    private static final boolean LOGD = NetworkPolicyLogger.LOGD;
+    private static final boolean LOGV = NetworkPolicyLogger.LOGV;
 
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADDED_SNOOZE = 2;
@@ -265,13 +272,6 @@
     private static final int VERSION_ADDED_CYCLE = 11;
     private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
 
-    /**
-     * Max items written to {@link #ProcStateSeqHistory}.
-     */
-    @VisibleForTesting
-    public static final int MAX_PROC_STATE_SEQ_HISTORY =
-            ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
-
     @VisibleForTesting
     public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
     @VisibleForTesting
@@ -471,13 +471,7 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
-    /**
-     * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
-     * the uid and procStateSeq will be written to this and will be printed as part of dump.
-     */
-    @VisibleForTesting
-    public ProcStateSeqHistory mObservedHistory
-            = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+    private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
 
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
@@ -966,6 +960,7 @@
                         .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
 
                 if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) {
+                    mLogger.meterednessChanged(network.netId, newMetered);
                     mNetworkMetered.put(network.netId, newMetered);
                     updateNetworkRulesNL();
                 }
@@ -2148,6 +2143,7 @@
                 final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
                 if (oldPolicy != policy) {
                     setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                    mLogger.uidPolicyChanged(uid, oldPolicy, policy);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -2168,6 +2164,7 @@
             policy |= oldPolicy;
             if (oldPolicy != policy) {
                 setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                mLogger.uidPolicyChanged(uid, oldPolicy, policy);
             }
         }
     }
@@ -2185,6 +2182,7 @@
             policy = oldPolicy & ~policy;
             if (oldPolicy != policy) {
                 setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                mLogger.uidPolicyChanged(uid, oldPolicy, policy);
             }
         }
     }
@@ -2264,7 +2262,7 @@
      */
     boolean removeUserStateUL(int userId, boolean writePolicy) {
 
-        if (LOGV) Slog.v(TAG, "removeUserStateUL()");
+        mLogger.removingUserState(userId);
         boolean changed = false;
 
         // Remove entries from revoked default restricted background UID whitelist
@@ -2429,7 +2427,6 @@
     @Override
     public void onTetheringChanged(String iface, boolean tethering) {
         // No need to enforce permission because setRestrictBackground() will do it.
-        if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")");
         synchronized (mUidRulesFirstLock) {
             if (mRestrictBackground && tethering) {
                 Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
@@ -2486,6 +2483,7 @@
             }
 
             sendRestrictBackgroundChangedMsg();
+            mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground);
 
             if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
                 mRestrictBackgroundChangedInBsm = true;
@@ -2551,6 +2549,7 @@
                     return;
                 }
                 mDeviceIdleMode = enabled;
+                mLogger.deviceIdleModeEnabled(enabled);
                 if (mSystemReady) {
                     // Device idle change means we need to rebuild rules for all
                     // known apps, so do a global refresh.
@@ -2964,10 +2963,7 @@
                 }
                 fout.decreaseIndent();
 
-                fout.println("Observed uid state changes:");
-                fout.increaseIndent();
-                mObservedHistory.dumpUL(fout);
-                fout.decreaseIndent();
+                mLogger.dumpLogs(fout);
             }
         }
     }
@@ -3750,8 +3746,8 @@
             try {
                 final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-                if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
                 synchronized (mUidRulesFirstLock) {
+                    mLogger.appIdleStateChanged(uid, idle);
                     updateRuleForAppIdleUL(uid);
                     updateRulesForPowerRestrictionsUL(uid);
                 }
@@ -3762,6 +3758,7 @@
         @Override
         public void onParoleStateChanged(boolean isParoleOn) {
             synchronized (mUidRulesFirstLock) {
+                mLogger.paroleStateChanged(isParoleOn);
                 updateRulesForAppIdleParoleUL();
             }
         }
@@ -3947,7 +3944,7 @@
             synchronized (mUidRulesFirstLock) {
                 // We received a uid state change callback, add it to the history so that it
                 // will be useful for debugging.
-                mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+                mLogger.uidStateChanged(uid, procState, procStateSeq);
                 // Now update the network policy rules as per the updated uid state.
                 updateUidStateUL(uid, procState);
                 // Updating the network rules is done, so notify AMS about this.
@@ -4081,6 +4078,7 @@
                 rules[index] = uidRules.valueAt(index);
             }
             mNetworkManager.setFirewallUidRules(chain, uids, rules);
+            mLogger.firewallRulesChanged(chain, uids, rules);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting firewall uid rules", e);
         } catch (RemoteException e) {
@@ -4107,6 +4105,7 @@
 
             try {
                 mNetworkManager.setFirewallUidRule(chain, uid, rule);
+                mLogger.uidFirewallRuleChanged(chain, uid, rule);
             } catch (IllegalStateException e) {
                 Log.wtf(TAG, "problem setting firewall uid rules", e);
             } catch (RemoteException e) {
@@ -4129,6 +4128,7 @@
         mFirewallChainStates.put(chain, enable);
         try {
             mNetworkManager.setFirewallChainEnabled(chain, enable);
+            mLogger.firewallChainEnabled(chain, enable);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem enable firewall chain", e);
         } catch (RemoteException e) {
@@ -4305,30 +4305,30 @@
             isBackgroundRestricted = mRestrictBackground;
         }
         if (hasRule(uidRules, RULE_REJECT_ALL)) {
-            if (LOGV) logUidStatus(uid, "blocked by power restrictions");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER);
             return true;
         }
         if (!isNetworkMetered) {
-            if (LOGV) logUidStatus(uid, "allowed on unmetered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED);
             return false;
         }
         if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            if (LOGV) logUidStatus(uid, "blacklisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST);
             return true;
         }
         if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            if (LOGV) logUidStatus(uid, "whitelisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST);
             return false;
         }
         if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST);
             return false;
         }
         if (isBackgroundRestricted) {
-            if (LOGV) logUidStatus(uid, "blocked when background is restricted");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT);
             return true;
         }
-        if (LOGV) logUidStatus(uid, "allowed by default");
+        mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT);
         return false;
     }
 
@@ -4379,6 +4379,7 @@
         @Override
         public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
             synchronized (mUidRulesFirstLock) {
+                mLogger.tempPowerSaveWlChanged(appId, added);
                 if (added) {
                     mPowerSaveTempWhitelistAppIds.put(appId, true);
                 } else {
@@ -4393,80 +4394,6 @@
         return (uidRules & rule) != 0;
     }
 
-    private static void logUidStatus(int uid, String descr) {
-        Slog.d(TAG, String.format("uid %d is %s", uid, descr));
-    }
-
-    /**
-     * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
-     * (uid, procStateSeq) pairs.
-     */
-    @VisibleForTesting
-    public static final class ProcStateSeqHistory {
-        private static final int INVALID_UID = -1;
-
-        /**
-         * Denotes maximum number of items this history can hold.
-         */
-        private final int mMaxCapacity;
-        /**
-         * Used for storing the uid information.
-         */
-        private final int[] mUids;
-        /**
-         * Used for storing the sequence numbers associated with {@link #mUids}.
-         */
-        private final long[] mProcStateSeqs;
-        /**
-         * Points to the next available slot for writing (uid, procStateSeq) pair.
-         */
-        private int mHistoryNext;
-
-        public ProcStateSeqHistory(int maxCapacity) {
-            mMaxCapacity = maxCapacity;
-            mUids = new int[mMaxCapacity];
-            Arrays.fill(mUids, INVALID_UID);
-            mProcStateSeqs = new long[mMaxCapacity];
-        }
-
-        @GuardedBy("mUidRulesFirstLock")
-        public void addProcStateSeqUL(int uid, long procStateSeq) {
-            mUids[mHistoryNext] = uid;
-            mProcStateSeqs[mHistoryNext] = procStateSeq;
-            mHistoryNext = increaseNext(mHistoryNext, 1);
-        }
-
-        @GuardedBy("mUidRulesFirstLock")
-        public void dumpUL(IndentingPrintWriter fout) {
-            if (mUids[0] == INVALID_UID) {
-                fout.println("NONE");
-                return;
-            }
-            int index = mHistoryNext;
-            do {
-                index = increaseNext(index, -1);
-                if (mUids[index] == INVALID_UID) {
-                    break;
-                }
-                fout.println(getString(mUids[index], mProcStateSeqs[index]));
-            } while (index != mHistoryNext);
-        }
-
-        public static String getString(int uid, long procStateSeq) {
-            return "UID=" + uid + " Seq=" + procStateSeq;
-        }
-
-        private int increaseNext(int next, int increment) {
-            next += increment;
-            if (next >= mMaxCapacity) {
-                next = 0;
-            } else if (next < 0) {
-                next = mMaxCapacity - 1;
-            }
-            return next;
-        }
-    }
-
     private class NotificationId {
         private final String mTag;
         private final int mId;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cf01400..bec6fc2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4743,7 +4743,8 @@
     }
 
     void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
-        if (!mAccessibilityManager.isEnabled()) {
+        if (!mAccessibilityManager.isObservedEventType(
+                AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8a9b45b..2d5f7c7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8045,24 +8045,24 @@
         return finalList;
     }
 
-    private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
+    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            scanDirLI(dir, parseFlags, scanFlags, currentTime);
+            scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
-    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
-        final File[] files = dir.listFiles();
+    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
+        final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
-            Log.d(TAG, "No files in app dir " + dir);
+            Log.d(TAG, "No files in app dir " + scanDir);
             return;
         }
 
         if (DEBUG_PACKAGE_SCANNING) {
-            Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
+            Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
         try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
@@ -8094,7 +8094,7 @@
                     }
                     try {
                         if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
-                            scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
+                            scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                                     currentTime, null);
                         }
                     } catch (PackageManagerException e) {
@@ -8126,14 +8126,14 @@
         logCriticalInfo(priority, msg);
     }
 
-    private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
+    private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
             final @ParseFlags int parseFlags) throws PackageManagerException {
         // When upgrading from pre-N MR1, verify the package time stamp using the package
         // directory and not the APK file.
         final long lastModifiedTime = mIsPreNMR1Upgrade
-                ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile);
+                ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
         if (ps != null
-                && ps.codePath.equals(srcFile)
+                && ps.codePathString.equals(pkg.codePath)
                 && ps.timeStamp == lastModifiedTime
                 && !isCompatSignatureUpdateNeeded(pkg)
                 && !isRecoverSignatureUpdateNeeded(pkg)) {
@@ -8156,7 +8156,7 @@
             Slog.w(TAG, "PackageSetting for " + ps.name
                     + " is missing signatures.  Collecting certs again to recover them.");
         } else {
-            Slog.i(TAG, srcFile.toString() + " changed; collecting certs");
+            Slog.i(TAG, toString() + " changed; collecting certs");
         }
 
         try {
@@ -8211,14 +8211,14 @@
             renameStaticSharedLibraryPackage(pkg);
         }
 
-        return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
+        return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
     }
 
     /**
      *  Scans a package and returns the newly parsed package.
      *  @throws PackageManagerException on a parse error.
      */
-    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
+    private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg,
             final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user)
                     throws PackageManagerException {
@@ -8236,20 +8236,20 @@
         }
 
         // Scan the parent
-        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, parseFlags,
+        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags,
                 scanFlags, currentTime, user);
 
         // Scan the children
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             PackageParser.Package childPackage = pkg.childPackages.get(i);
-            scanPackageInternalLI(childPackage, scanFile, parseFlags, scanFlags,
+            scanPackageInternalLI(childPackage, parseFlags, scanFlags,
                     currentTime, user);
         }
 
 
         if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
-            return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
+            return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
         }
 
         return scannedPkg;
@@ -8259,12 +8259,12 @@
      *  Scans a package and returns the newly parsed package.
      *  @throws PackageManagerException on a parse error.
      */
-    private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
+    private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg,
             @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user)
                     throws PackageManagerException {
         PackageSetting ps = null;
-        PackageSetting updatedPkg;
+        PackageSetting updatedPs;
         // reader
         synchronized (mPackages) {
             // Look to see if we already know about this package.
@@ -8281,8 +8281,8 @@
             // Check to see if this package could be hiding/updating a system
             // package.  Must look for it either under the original or real
             // package name depending on our state.
-            updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
-            if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
+            updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+            if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs);
 
             // If this is a package we don't know about on the system partition, we
             // may need to remove disabled child packages on the system partition
@@ -8316,27 +8316,27 @@
             }
         }
 
-        final boolean isUpdatedPkg = updatedPkg != null;
+        final boolean isUpdatedPkg = updatedPs != null;
         final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0;
         boolean isUpdatedPkgBetter = false;
         // First check if this is a system package that may involve an update
         if (isUpdatedSystemPkg) {
             // If new package is not located in "/system/priv-app" (e.g. due to an OTA),
             // it needs to drop FLAG_PRIVILEGED.
-            if (locationIsPrivileged(scanFile)) {
-                updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+            if (locationIsPrivileged(pkg.codePath)) {
+                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
             } else {
-                updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
             }
             // If new package is not located in "/oem" (e.g. due to an OTA),
             // it needs to drop FLAG_OEM.
-            if (locationIsOem(scanFile)) {
-                updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
+            if (locationIsOem(pkg.codePath)) {
+                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
             } else {
-                updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
+                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
             }
 
-            if (ps != null && !ps.codePath.equals(scanFile)) {
+            if (ps != null && !ps.codePathString.equals(pkg.codePath)) {
                 // The path has changed from what was last scanned...  check the
                 // version of the new path against what we have stored to determine
                 // what to do.
@@ -8344,26 +8344,27 @@
                 if (pkg.mVersionCode <= ps.versionCode) {
                     // The system package has been updated and the code path does not match
                     // Ignore entry. Skip it.
-                    if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
+                    if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath
                             + " ignored: updated version " + ps.versionCode
                             + " better than this " + pkg.mVersionCode);
-                    if (!updatedPkg.codePath.equals(scanFile)) {
+                    if (!updatedPs.codePathString.equals(pkg.codePath)) {
                         Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
-                                + ps.name + " changing from " + updatedPkg.codePathString
-                                + " to " + scanFile);
-                        updatedPkg.codePath = scanFile;
-                        updatedPkg.codePathString = scanFile.toString();
-                        updatedPkg.resourcePath = scanFile;
-                        updatedPkg.resourcePathString = scanFile.toString();
+                                + ps.name + " changing from " + updatedPs.codePathString
+                                + " to " + pkg.codePath);
+                        final File codePath = new File(pkg.codePath);
+                        updatedPs.codePath = codePath;
+                        updatedPs.codePathString = pkg.codePath;
+                        updatedPs.resourcePath = codePath;
+                        updatedPs.resourcePathString = pkg.codePath;
                     }
-                    updatedPkg.pkg = pkg;
-                    updatedPkg.versionCode = pkg.mVersionCode;
+                    updatedPs.pkg = pkg;
+                    updatedPs.versionCode = pkg.mVersionCode;
 
                     // Update the disabled system child packages to point to the package too.
-                    final int childCount = updatedPkg.childPackageNames != null
-                            ? updatedPkg.childPackageNames.size() : 0;
+                    final int childCount = updatedPs.childPackageNames != null
+                            ? updatedPs.childPackageNames.size() : 0;
                     for (int i = 0; i < childCount; i++) {
-                        String childPackageName = updatedPkg.childPackageNames.get(i);
+                        String childPackageName = updatedPs.childPackageNames.get(i);
                         PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
                                 childPackageName);
                         if (updatedChildPkg != null) {
@@ -8384,7 +8385,7 @@
                         mPackages.remove(ps.name);
                     }
 
-                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
                             + " reverting from " + ps.codePathString
                             + ": new version " + pkg.mVersionCode
                             + " better than installed " + ps.versionCode);
@@ -8431,16 +8432,16 @@
         if (isUpdatedSystemPkg && !isUpdatedPkgBetter) {
             // Set CPU Abis to application info.
             if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
-                final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPkg);
-                derivePackageAbi(pkg, scanFile, cpuAbiOverride, false, mAppLib32InstallDir);
+                final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs);
+                derivePackageAbi(pkg, cpuAbiOverride, false, mAppLib32InstallDir);
             } else {
-                pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
-                pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
+                pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString;
+                pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString;
             }
-            pkg.mExtras = updatedPkg;
+            pkg.mExtras = updatedPs;
 
             throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
-                    + scanFile + " ignored: updated version " + ps.versionCode
+                    + pkg.codePath + " ignored: updated version " + ps.versionCode
                     + " better than this " + pkg.mVersionCode);
         }
 
@@ -8450,19 +8451,19 @@
 
             // An updated privileged application will not have the PARSE_IS_PRIVILEGED
             // flag set initially
-            if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
                 scanFlags |= SCAN_AS_PRIVILEGED;
             }
 
             // An updated OEM app will not have the PARSE_IS_OEM
             // flag set initially
-            if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
                 scanFlags |= SCAN_AS_OEM;
             }
         }
 
         // Verify certificates against what was last scanned
-        collectCertificatesLI(ps, pkg, scanFile, parseFlags);
+        collectCertificatesLI(ps, pkg, parseFlags);
 
         /*
          * A new system app appeared, but we already had a non-system one of the
@@ -8492,7 +8493,7 @@
                  */
                 if (pkg.mVersionCode <= ps.versionCode) {
                     shouldHideSystemApp = true;
-                    logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+                    logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath
                             + " but new version " + pkg.mVersionCode + " better than installed "
                             + ps.versionCode + "; hiding system");
                 } else {
@@ -8502,7 +8503,7 @@
                      * already-installed application and replace it with our own
                      * while keeping the application data.
                      */
-                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
                             + " reverting from " + ps.codePathString + ": new version "
                             + pkg.mVersionCode + " better than installed " + ps.versionCode);
                     InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
@@ -9976,8 +9977,7 @@
             if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
                 final boolean extractNativeLibs = !pkg.isLibrary();
-                derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs,
-                        mAppLib32InstallDir);
+                derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
                 // Some system apps still use directory structure for native libraries
@@ -10084,7 +10084,7 @@
         }
 
         // Take care of first install / last update times.
-        final long scanFileTime = getLastModifiedTime(pkg, scanFile);
+        final long scanFileTime = getLastModifiedTime(pkg);
         if (currentTime != 0) {
             if (pkgSetting.firstInstallTime == 0) {
                 pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
@@ -10864,10 +10864,9 @@
      *
      * If {@code extractLibs} is true, native libraries are extracted from the app if required.
      */
-    private static void derivePackageAbi(PackageParser.Package pkg, File scanFile,
-                                 String cpuAbiOverride, boolean extractLibs,
-                                 File appLib32InstallDir)
-            throws PackageManagerException {
+    private static void derivePackageAbi(PackageParser.Package pkg, String cpuAbiOverride,
+            boolean extractLibs, File appLib32InstallDir)
+                    throws PackageManagerException {
         // Give ourselves some initial paths; we'll come back for another
         // pass once we've determined ABI below.
         setNativeLibraryPaths(pkg, appLib32InstallDir);
@@ -15000,7 +14999,12 @@
             resourceFile = afterCodeFile;
 
             // Reflect the rename in scanned details
-            pkg.setCodePath(afterCodeFile.getAbsolutePath());
+            try {
+                pkg.setCodePath(afterCodeFile.getCanonicalPath());
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
+                return false;
+            }
             pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
                     afterCodeFile, pkg.baseCodePath));
             pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
@@ -16482,8 +16486,7 @@
                 String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
                     args.abiOverride : pkg.cpuAbiOverride);
                 final boolean extractNativeLibs = !pkg.isLibrary();
-                derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
-                        extractNativeLibs, mAppLib32InstallDir);
+                derivePackageAbi(pkg, abiOverride, extractNativeLibs, mAppLib32InstallDir);
             } catch (PackageManagerException pme) {
                 Slog.e(TAG, "Error deriving application ABI", pme);
                 res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
@@ -17487,21 +17490,19 @@
         }
     }
 
-    static boolean locationIsPrivileged(File path) {
+    static boolean locationIsPrivileged(String path) {
         try {
-            final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app")
-                    .getCanonicalPath();
-            return path.getCanonicalPath().startsWith(privilegedAppDir);
+            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
+            return path.startsWith(privilegedAppDir.getCanonicalPath());
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
         return false;
     }
 
-    static boolean locationIsOem(File path) {
+    static boolean locationIsOem(String path) {
         try {
-            return path.getCanonicalPath().startsWith(
-                    Environment.getOemDirectory().getCanonicalPath());
+            return path.startsWith(Environment.getOemDirectory().getCanonicalPath());
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
@@ -17597,7 +17598,7 @@
         // Install the system package
         if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
         try {
-            installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles,
+            installPackageFromSystemLIF(disabledPs.codePathString, false, allUserHandles,
                     outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
@@ -17614,7 +17615,7 @@
     /**
      * Installs a package that's already on the system partition.
      */
-    private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath,
+    private PackageParser.Package installPackageFromSystemLIF(@NonNull String codePathString,
             boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles,
             @Nullable PermissionsState origPermissionState, boolean writeSettings)
                     throws PackageManagerException {
@@ -17623,13 +17624,14 @@
                 | PackageParser.PARSE_MUST_BE_APK
                 | PackageParser.PARSE_IS_SYSTEM_DIR;
         @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
-        if (isPrivileged || locationIsPrivileged(codePath)) {
+        if (isPrivileged || locationIsPrivileged(codePathString)) {
             scanFlags |= SCAN_AS_PRIVILEGED;
         }
-        if (locationIsOem(codePath)) {
+        if (locationIsOem(codePathString)) {
             scanFlags |= SCAN_AS_OEM;
         }
 
+        final File codePath = new File(codePathString);
         final PackageParser.Package pkg =
                 scanPackageTracedLI(codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
 
@@ -19741,7 +19743,7 @@
                                 // until we can disable the package later.
                                 enableSystemPackageLPw(deletedPkg);
                             }
-                            installPackageFromSystemLIF(new File(deletedPkg.codePath),
+                            installPackageFromSystemLIF(deletedPkg.codePath,
                                     false /*isPrivileged*/, null /*allUserHandles*/,
                                     null /*origUserHandles*/, null /*origPermissionsState*/,
                                     true /*writeSettings*/);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 758abd7..20ec9b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -295,19 +295,20 @@
         return false;
     }
 
-    public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
-        if (srcFile.isDirectory()) {
-            final File baseFile = new File(pkg.baseCodePath);
-            long maxModifiedTime = baseFile.lastModified();
-            if (pkg.splitCodePaths != null) {
-                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
-                    final File splitFile = new File(pkg.splitCodePaths[i]);
-                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
-                }
-            }
-            return maxModifiedTime;
+    public static long getLastModifiedTime(PackageParser.Package pkg) {
+        final File srcFile = new File(pkg.codePath);
+        if (!srcFile.isDirectory()) {
+            return srcFile.lastModified();
         }
-        return srcFile.lastModified();
+        final File baseFile = new File(pkg.baseCodePath);
+        long maxModifiedTime = baseFile.lastModified();
+        if (pkg.splitCodePaths != null) {
+            for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+                final File splitFile = new File(pkg.splitCodePaths[i]);
+                maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+            }
+        }
+        return maxModifiedTime;
     }
 
     /**
@@ -570,7 +571,7 @@
             if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                         "Package " + packageName +
-                        " signatures don't match previously installed version; ignoring!");
+                        " signatures do not match previously installed version; ignoring!");
             }
         }
         // Check for shared user signatures
@@ -578,16 +579,16 @@
             // Already existing package. Make sure signatures match
             boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                     parsedSignatures) == PackageManager.SIGNATURE_MATCH;
-            if (!match) {
+            if (!match && compareCompat) {
                 match = matchSignaturesCompat(
                         packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
             }
-            if (!match && compareCompat) {
+            if (!match && compareRecover) {
                 match = matchSignaturesRecover(
                         packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
                 compatMatch |= match;
             }
-            if (!match && compareRecover) {
+            if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                         "Package " + packageName
                         + " has no signatures that match those in shared user "
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7077fd1..ddad677 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3572,11 +3572,10 @@
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
         pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
-        final File codePathFile = new File(codePathStr);
-        if (PackageManagerService.locationIsPrivileged(codePathFile)) {
+        if (PackageManagerService.locationIsPrivileged(codePathStr)) {
             pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
         }
-        PackageSetting ps = new PackageSetting(name, realName, codePathFile,
+        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
                 new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
                 secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
                 parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ddd3bbd..7415ec3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -240,6 +240,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
@@ -1053,7 +1054,8 @@
 
     private ImmersiveModeConfirmation mImmersiveModeConfirmation;
 
-    private SystemGesturesPointerEventListener mSystemGestures;
+    @VisibleForTesting
+    SystemGesturesPointerEventListener mSystemGestures;
 
     IStatusBarService getStatusBarService() {
         synchronized (mServiceAquireLock) {
@@ -2664,17 +2666,21 @@
             // The status bar is the only window allowed to exhibit keyguard behavior.
             attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
+    }
 
+    private int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
+        int impliedFlags = 0;
         if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
-            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+            impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
         }
         final boolean forceWindowDrawsStatusBarBackground =
                 (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
         if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                 || forceWindowDrawsStatusBarBackground
                         && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
-            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         }
+        return impliedFlags;
     }
 
     void readLidState() {
@@ -2724,7 +2730,7 @@
     @Override
     public void onConfigurationChanged() {
         // TODO(multi-display): Define policy for secondary displays.
-        Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+        Context uiContext = getSystemUiContext();
         final Resources res = uiContext.getResources();
 
         mStatusBarHeight =
@@ -2765,6 +2771,11 @@
         }
     }
 
+    @VisibleForTesting
+    Context getSystemUiContext() {
+        return ActivityThread.currentActivityThread().getSystemUiContext();
+    }
+
     @Override
     public int getMaxWallpaperLayer() {
         return getWindowLayerFromTypeLw(TYPE_STATUS_BAR);
@@ -4825,7 +4836,8 @@
         final int fl = PolicyControl.getWindowFlags(win, attrs);
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
-        final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+        final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+        final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
 
         final Rect pf = mTmpParentFrame;
         final Rect df = mTmpDisplayFrame;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 8d56eb8..5f067d4 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -213,7 +213,7 @@
          */
         public void computeFrameLw(Rect parentFrame, Rect displayFrame,
                 Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame,
-                Rect stableFrame, Rect outsetFrame);
+                Rect stableFrame, @Nullable Rect outsetFrame);
 
         /**
          * Retrieve the current frame of the window that has been assigned by
@@ -808,11 +808,11 @@
             case TYPE_INPUT_METHOD_DIALOG:
                 // on-screen keyboards and other such input method user interfaces go here.
                 return  15;
-            case TYPE_STATUS_BAR_SUB_PANEL:
-                return  17;
             case TYPE_STATUS_BAR:
-                return  18;
+                return  17;
             case TYPE_STATUS_BAR_PANEL:
+                return  18;
+            case TYPE_STATUS_BAR_SUB_PANEL:
                 return  19;
             case TYPE_KEYGUARD_DIALOG:
                 return  20;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 87c9274..7c234f9 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -69,6 +69,7 @@
     private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
     private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
     private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
+    private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
     private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
 
     private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
@@ -190,6 +191,12 @@
     private boolean mForceAllAppsStandby;
 
     /**
+     * Whether to put all apps in the stand-by mode.
+     */
+    @GuardedBy("mLock")
+    private boolean mForceBackgroundCheck;
+
+    /**
      * Weather to show non-essential sensors (e.g. edge sensors) or not.
      */
     @GuardedBy("mLock")
@@ -326,6 +333,7 @@
         mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
         mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
         mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
+        mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
         mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
 
         // Get default value from Settings.Secure
@@ -395,6 +403,12 @@
                 case ServiceType.VIBRATION:
                     return builder.setBatterySaverEnabled(mVibrationDisabled)
                             .build();
+                case ServiceType.FORCE_ALL_APPS_STANDBY:
+                    return builder.setBatterySaverEnabled(mForceAllAppsStandby)
+                            .build();
+                case ServiceType.FORCE_BACKGROUND_CHECK:
+                    return builder.setBatterySaverEnabled(mForceBackgroundCheck)
+                            .build();
                 case ServiceType.OPTIONAL_SENSORS:
                     return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
                             .build();
@@ -438,6 +452,7 @@
             pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
             pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
             pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
+            pw.println("  " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
             pw.println("  " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
             pw.println();
 
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index b4300a9..d118c4e 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,3 +1,4 @@
 michaelwr@google.com
 
 per-file BatterySaverPolicy.java=omakoto@google.com
+per-file ShutdownThread.java=fkupolov@google.com
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 6bf725e..6fb345b 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -20,10 +20,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityManager;
-import android.app.KeyguardManager;
 import android.app.ProgressDialog;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.IBluetoothManager;
 import android.content.BroadcastReceiver;
@@ -31,8 +28,6 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
 import android.media.AudioAttributes;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -47,8 +42,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
-import android.os.storage.IStorageManager;
-import android.os.storage.IStorageShutdownObserver;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.TimingsTraceLog;
@@ -123,7 +116,6 @@
     private static String METRIC_RADIOS = "shutdown_radios";
     private static String METRIC_BT = "shutdown_bt";
     private static String METRIC_RADIO = "shutdown_radio";
-    private static String METRIC_SM = "shutdown_storage_manager";
 
     private final Object mActionDoneSync = new Object();
     private boolean mActionDone;
@@ -526,54 +518,6 @@
         shutdownTimingLog.traceEnd(); // ShutdownRadios
         metricEnded(METRIC_RADIOS);
 
-        // Shutdown StorageManagerService to ensure media is in a safe state
-        IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
-            public void onShutDownComplete(int statusCode) throws RemoteException {
-                Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
-                actionDone();
-            }
-        };
-
-        Log.i(TAG, "Shutting down StorageManagerService");
-        shutdownTimingLog.traceBegin("ShutdownStorageManager");
-        metricStarted(METRIC_SM);
-
-        // Set initial variables and time out time.
-        mActionDone = false;
-        final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
-        synchronized (mActionDoneSync) {
-            try {
-                final IStorageManager storageManager = IStorageManager.Stub.asInterface(
-                        ServiceManager.checkService("mount"));
-                if (storageManager != null) {
-                    storageManager.shutdown(observer);
-                } else {
-                    Log.w(TAG, "StorageManagerService unavailable for shutdown");
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Exception during StorageManagerService shutdown", e);
-            }
-            while (!mActionDone) {
-                long delay = endShutTime - SystemClock.elapsedRealtime();
-                if (delay <= 0) {
-                    Log.w(TAG, "StorageManager shutdown wait timed out");
-                    break;
-                } else if (mRebootHasProgressBar) {
-                    int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
-                            (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
-                            MAX_SHUTDOWN_WAIT_TIME);
-                    status += RADIO_STOP_PERCENT;
-                    sInstance.setRebootProgress(status, null);
-                }
-                try {
-                    mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-        shutdownTimingLog.traceEnd(); // ShutdownStorageManager
-        metricEnded(METRIC_SM);
-
         if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
 
@@ -585,6 +529,7 @@
         shutdownTimingLog.traceEnd(); // SystemServerShutdown
         metricEnded(METRIC_SYSTEM_SERVER);
         saveMetrics(mReboot);
+        // Remaining work will be done by init, including vold shutdown
         rebootOrShutdown(mContext, mReboot, mReason);
     }
 
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index ae01ea57..80bc935 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -16,6 +16,7 @@
 package com.android.server.power.batterysaver;
 
 import android.Manifest;
+import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -109,6 +110,9 @@
         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         mContext.registerReceiver(mReceiver, filter);
+
+        mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class)
+                .isRuntimeRestarted());
     }
 
     private PowerManager getPowerManager() {
@@ -217,13 +221,6 @@
             pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
         }
 
-        if (enabled) {
-            // STOPSHIP Remove the toast.
-            Toast.makeText(mContext,
-                    com.android.internal.R.string.battery_saver_warning,
-                    Toast.LENGTH_LONG).show();
-        }
-
         if (ArrayUtils.isEmpty(fileValues)) {
             mFileUpdater.restoreDefault();
         } else {
@@ -231,6 +228,13 @@
         }
 
         if (sendBroadcast) {
+            if (enabled) {
+                // STOPSHIP Remove the toast.
+                Toast.makeText(mContext,
+                        com.android.internal.R.string.battery_saver_warning,
+                        Toast.LENGTH_LONG).show();
+            }
+
             if (DEBUG) {
                 Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
             }
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index cc1b540..e0ab9e9 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,19 +16,34 @@
 package com.android.server.power.batterysaver;
 
 import android.content.Context;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.server.IoThread;
 
 import libcore.io.IoUtils;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Map;
 
@@ -47,6 +62,14 @@
 
     private static final boolean DEBUG = BatterySaverController.DEBUG;
 
+    /**
+     * If this system property is set to 1, it'll skip all file writes. This can be used when
+     * one needs to change max CPU frequency for benchmarking, for example.
+     */
+    private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files";
+
+    private static final String TAG_DEFAULT_ROOT = "defaults";
+
     // Don't do disk access with this lock held.
     private final Object mLock = new Object();
 
@@ -93,6 +116,21 @@
         RETRY_INTERVAL_MS = retryIntervalMs;
     }
 
+    public void systemReady(boolean runtimeRestarted) {
+        synchronized (mLock) {
+            if (runtimeRestarted) {
+                // If it runtime restarted, read the original values from the disk and apply.
+                if (loadDefaultValuesLocked()) {
+                    Slog.d(TAG, "Default values loaded after runtime restart; writing them...");
+                    restoreDefault();
+                }
+            } else {
+                // Delete it, without checking the result. (file-not-exist is not an exception.)
+                injectDefaultValuesFilename().delete();
+            }
+        }
+    }
+
     /**
      * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
      */
@@ -241,6 +279,7 @@
         }
         synchronized (mLock) {
             mDefaultValues.put(file, originalValue);
+            saveDefaultValuesLocked();
         }
         return true;
     }
@@ -252,17 +291,92 @@
 
     @VisibleForTesting
     void injectWriteToFile(String file, String value) throws IOException {
+        if (injectShouldSkipWrite()) {
+            Slog.i(TAG, "Skipped writing to '" + file + "'");
+            return;
+        }
         if (DEBUG) {
             Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
         }
         try (FileWriter out = new FileWriter(file)) {
             out.write(value);
-        } catch (IOException e) {
+        } catch (IOException | RuntimeException e) {
             Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
             throw e;
         }
     }
 
+    private void saveDefaultValuesLocked() {
+        final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+
+        FileOutputStream outs = null;
+        try {
+            file.getBaseFile().getParentFile().mkdirs();
+            outs = file.startWrite();
+
+            // Write to XML
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(outs, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_DEFAULT_ROOT);
+
+            XmlUtils.writeMapXml(mDefaultValues, out, null);
+
+            // Epilogue.
+            out.endTag(null, TAG_DEFAULT_ROOT);
+            out.endDocument();
+
+            // Close.
+            file.finishWrite(outs);
+        } catch (IOException | XmlPullParserException | RuntimeException e) {
+            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+            file.failWrite(outs);
+        }
+    }
+
+    @VisibleForTesting
+    boolean loadDefaultValuesLocked() {
+        final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+        if (DEBUG) {
+            Slog.d(TAG, "Loading from " + file.getBaseFile());
+        }
+        Map<String, String> read = null;
+        try (FileInputStream in = file.openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                final int depth = parser.getDepth();
+                // Check the root tag
+                final String tag = parser.getName();
+                if (depth == 1) {
+                    if (!TAG_DEFAULT_ROOT.equals(tag)) {
+                        Slog.e(TAG, "Invalid root tag: " + tag);
+                        return false;
+                    }
+                    continue;
+                }
+                final String[] tagName = new String[1];
+                read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser,
+                        TAG_DEFAULT_ROOT, tagName, null);
+            }
+        } catch (FileNotFoundException e) {
+            read = null;
+        } catch (IOException | XmlPullParserException | RuntimeException e) {
+            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+        }
+        if (read != null) {
+            mDefaultValues.clear();
+            mDefaultValues.putAll(read);
+            return true;
+        }
+        return false;
+    }
+
     private void doWtf(String message) {
         injectWtf(message, null);
     }
@@ -271,4 +385,20 @@
     void injectWtf(String message, Throwable e) {
         Slog.wtf(TAG, message, e);
     }
+
+    File injectDefaultValuesFilename() {
+        final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver");
+        dir.mkdirs();
+        return new File(dir, "default-values.xml");
+    }
+
+    @VisibleForTesting
+    boolean injectShouldSkipWrite() {
+        return SystemProperties.getBoolean(PROP_SKIP_WRITE, false);
+    }
+
+    @VisibleForTesting
+    ArrayMap<String, String> getDefaultValuesForTest() {
+        return mDefaultValues;
+    }
 }
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 3772371..e240ec5 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -523,6 +523,18 @@
         sayHiToStatsd(); // tell statsd that we're ready too and link to it
     }
 
+    @Override
+    public void triggerUidSnapshot() {
+      enforceCallingPermission();
+      synchronized (sStatsdLock) {
+        try {
+          informAllUidsLocked(mContext);
+        } catch (RemoteException e) {
+          Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+        }
+      }
+    }
+
     private void enforceCallingPermission() {
         if (Binder.getCallingPid() == Process.myPid()) {
             return;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b07fe98..3792bc6 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -90,6 +90,13 @@
 
     boolean showShutdownUi(boolean isReboot, String requestString);
 
+    /**
+     * Show a rotation suggestion that a user may approve to rotate the screen.
+     *
+     * @param rotation rotation suggestion
+     */
+    void onProposedRotationChanged(int rotation);
+
     public interface GlobalActionsListener {
         /**
          * Called when sysui starts and connects its status bar, or when the status bar binder
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c78a340..c7c03b4 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -406,6 +406,15 @@
             }
             return false;
         }
+
+        @Override
+        public void onProposedRotationChanged(int rotation) {
+            if (mBar != null){
+                try {
+                    mBar.onProposedRotationChanged(rotation);
+                } catch (RemoteException ex) {}
+            }
+        }
     };
 
     // ================================================================================
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3fb3773..41348ba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -181,8 +181,8 @@
     private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
     // Contains all non-app window containers that should be displayed above the app containers
     // (e.g. Status bar)
-    private final NonAppWindowContainers mAboveAppWindowsContainers =
-            new NonAppWindowContainers("mAboveAppWindowsContainers");
+    private final AboveAppWindowContainers mAboveAppWindowsContainers =
+            new AboveAppWindowContainers("mAboveAppWindowsContainers");
     // Contains all non-app window containers that should be displayed below the app containers
     // (e.g. Wallpaper).
     private final NonAppWindowContainers mBelowAppWindowsContainers =
@@ -356,7 +356,8 @@
     /**
      * We organize all top-level Surfaces in to the following layers.
      * mOverlayLayer contains a few Surfaces which are always on top of others
-     * and omitted from Screen-Magnification ({@link WindowState#isScreenOverlay})
+     * and omitted from Screen-Magnification, for example the strict mode flash or
+     * the magnification overlay itself.
      * {@link #mWindowingLayer} contains everything else.
      */
     private SurfaceControl mOverlayLayer;
@@ -2968,227 +2969,30 @@
      * Takes a snapshot of the display.  In landscape mode this grabs the whole screen.
      * In portrait mode, it grabs the full screenshot.
      *
-     * @param width the width of the target bitmap
-     * @param height the height of the target bitmap
-     * @param includeFullDisplay true if the screen should not be cropped before capture
-     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
      * @param config of the output bitmap
      * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
-     * @param includeDecor whether to include window decors, like the status or navigation bar
-     *                     background of the window
      */
-    Bitmap screenshotApplications(IBinder appToken, int width, int height,
-            boolean includeFullDisplay, float frameScale, Bitmap.Config config,
-            boolean wallpaperOnly, boolean includeDecor) {
-        Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
-                frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
-        if (bitmap == null) {
-            return null;
-        }
-
-        if (DEBUG_SCREENSHOT) {
-            // TEST IF IT's ALL BLACK
-            int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
-            bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
-                    bitmap.getHeight());
-            boolean allBlack = true;
-            final int firstColor = buffer[0];
-            for (int i = 0; i < buffer.length; i++) {
-                if (buffer[i] != firstColor) {
-                    allBlack = false;
-                    break;
-                }
-            }
-            if (allBlack) {
-                final WindowState appWin = mScreenshotApplicationState.appWin;
-                final int maxLayer = mScreenshotApplicationState.maxLayer;
-                final int minLayer = mScreenshotApplicationState.minLayer;
-                Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
-                        Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
-                        (appWin != null ?
-                                appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
-                        " minLayer=" + minLayer + " maxLayer=" + maxLayer);
-            }
-        }
-
-        // Create a copy of the screenshot that is immutable and backed in ashmem.
-        // This greatly reduces the overhead of passing the bitmap between processes.
-        Bitmap ret = bitmap.createAshmemBitmap(config);
-        bitmap.recycle();
-        return ret;
-    }
-
-    GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
-            boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
-            boolean includeDecor) {
-        return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
-                wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
-    }
-
-    private <E> E screenshotApplications(IBinder appToken, int width, int height,
-            boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
-            boolean includeDecor, Screenshoter<E> screenshoter) {
-        int dw = mDisplayInfo.logicalWidth;
-        int dh = mDisplayInfo.logicalHeight;
-        if (dw == 0 || dh == 0) {
-            if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                    + ": returning null. logical widthxheight=" + dw + "x" + dh);
-            return null;
-        }
-
-        E bitmap;
-
-        mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
-        final Rect frame = new Rect();
-        final Rect stackBounds = new Rect();
-
-        final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1)
-                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
-        final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay);
-        synchronized(mService.mWindowMap) {
+    Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) {
+        synchronized (mService.mWindowMap) {
             if (!mService.mPolicy.isScreenOn()) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display"
-                        + " was off.");
-                return null;
-            }
-            // Figure out the part of the screen that is actually the app.
-            mScreenshotApplicationState.appWin = null;
-            forAllWindows(w -> {
-                if (!w.mHasSurface) {
-                    return false;
+                if (DEBUG_SCREENSHOT) {
+                    Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
                 }
-                if (w.mLayer >= aboveAppLayer) {
-                    return false;
-                }
-                if (wallpaperOnly && !w.mIsWallpaper) {
-                    return false;
-                }
-                if (w.mIsImWindow) {
-                    return false;
-                } else if (w.mIsWallpaper) {
-                    // If this is the wallpaper layer and we're only looking for the wallpaper layer
-                    // then the target window state is this one.
-                    if (wallpaperOnly) {
-                        mScreenshotApplicationState.appWin = w;
-                    }
-
-                    if (mScreenshotApplicationState.appWin == null) {
-                        // We have not ran across the target window yet, so it is probably behind
-                        // the wallpaper. This can happen when the keyguard is up and all windows
-                        // are moved behind the wallpaper. We don't want to include the wallpaper
-                        // layer in the screenshot as it will cover-up the layer of the target
-                        // window.
-                        return false;
-                    }
-                    // Fall through. The target window is in front of the wallpaper. For this
-                    // case we want to include the wallpaper layer in the screenshot because
-                    // the target window might have some transparent areas.
-                } else if (appToken != null) {
-                    if (w.mAppToken == null || w.mAppToken.token != appToken) {
-                        // This app window is of no interest if it is not associated with the
-                        // screenshot app.
-                        return false;
-                    }
-                    mScreenshotApplicationState.appWin = w;
-                }
-
-                // Include this window.
-
-                final WindowStateAnimator winAnim = w.mWinAnimator;
-
-                // Don't include wallpaper in bounds calculation
-                if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
-                    if (includeDecor) {
-                        final Task task = w.getTask();
-                        if (task != null) {
-                            task.getBounds(frame);
-                        } else {
-
-                            // No task bounds? Too bad! Ain't no screenshot then.
-                            return true;
-                        }
-                    } else {
-                        final Rect wf = w.mFrame;
-                        final Rect cr = w.mContentInsets;
-                        int left = wf.left + cr.left;
-                        int top = wf.top + cr.top;
-                        int right = wf.right - cr.right;
-                        int bottom = wf.bottom - cr.bottom;
-                        frame.union(left, top, right, bottom);
-                        w.getVisibleBounds(stackBounds);
-                        if (!Rect.intersects(frame, stackBounds)) {
-                            // Set frame empty if there's no intersection.
-                            frame.setEmpty();
-                        }
-                    }
-                }
-
-                final boolean foundTargetWs =
-                        (w.mAppToken != null && w.mAppToken.token == appToken)
-                                || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
-                if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
-                    mScreenshotApplicationState.screenshotReady = true;
-                }
-
-                if (w.isObscuringDisplay()){
-                    return true;
-                }
-                return false;
-            }, true /* traverseTopToBottom */);
-
-            final WindowState appWin = mScreenshotApplicationState.appWin;
-            final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
-
-            if (appToken != null && appWin == null) {
-                // Can't find a window to snapshot.
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM,
-                        "Screenshot: Couldn't find a surface matching " + appToken);
                 return null;
             }
 
-            if (!screenshotReady) {
-                Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken +
-                        " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
-                        appWin.mWinAnimator.mDrawState)));
+            if (wallpaperOnly && !shouldScreenshotWallpaper()) {
                 return null;
             }
 
-            // Screenshot is ready to be taken. Everything from here below will continue
-            // through the bottom of the loop and return a value. We only stay in the loop
-            // because we don't want to release the mWindowMap lock until the screenshot is
-            // taken.
+            int dw = mDisplayInfo.logicalWidth;
+            int dh = mDisplayInfo.logicalHeight;
 
-
-            if (!mutableIncludeFullDisplay.value) {
-                // Constrain frame to the screen size.
-                if (!frame.intersect(0, 0, dw, dh)) {
-                    frame.setEmpty();
-                }
-            } else {
-                // Caller just wants entire display.
-                frame.set(0, 0, dw, dh);
-            }
-            if (frame.isEmpty()) {
+            if (dw <= 0 || dh <= 0) {
                 return null;
             }
 
-            if (width < 0) {
-                width = (int) (frame.width() * frameScale);
-            }
-            if (height < 0) {
-                height = (int) (frame.height() * frameScale);
-            }
-
-            // Tell surface flinger what part of the image to crop. Take the top
-            // right part of the application, and crop the larger dimension to fit.
-            Rect crop = new Rect(frame);
-            if (width / (float) frame.width() < height / (float) frame.height()) {
-                int cropWidth = (int)((float)width / (float)height * frame.height());
-                crop.right = crop.left + cropWidth;
-            } else {
-                int cropHeight = (int)((float)height / (float)width * frame.width());
-                crop.bottom = crop.top + cropHeight;
-            }
+            final Rect frame = new Rect(0, 0, dw, dh);
 
             // The screenshot API does not apply the current screen rotation.
             int rot = mDisplay.getRotation();
@@ -3197,43 +3001,52 @@
                 rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90;
             }
 
-            // Surfaceflinger is not aware of orientation, so convert our logical
-            // crop to surfaceflinger's portrait orientation.
-            convertCropForSurfaceFlinger(crop, rot, dw, dh);
-
-            if (DEBUG_SCREENSHOT) {
-                forAllWindows(w -> {
-                    final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
-                    Slog.i(TAG_WM, w + ": " + w.mLayer
-                            + " animLayer=" + w.mWinAnimator.mAnimLayer
-                            + " surfaceLayer=" + ((controller == null)
-                            ? "null" : controller.getLayer()));
-                }, false /* traverseTopToBottom */);
-            }
+            // SurfaceFlinger is not aware of orientation, so convert our logical
+            // crop to SurfaceFlinger's portrait orientation.
+            convertCropForSurfaceFlinger(frame, rot, dw, dh);
 
             final ScreenRotationAnimation screenRotationAnimation =
                     mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
             final boolean inRotation = screenRotationAnimation != null &&
                     screenRotationAnimation.isAnimating();
-            if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
-                    "Taking screenshot while rotating");
-
-            // We force pending transactions to flush before taking
-            // the screenshot by pushing an empty synchronous transaction.
-            SurfaceControl.openTransaction();
-            SurfaceControl.closeTransactionSync();
+            if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
 
             // TODO(b/68392460): We should screenshot Task controls directly
             // but it's difficult at the moment as the Task doesn't have the
             // correct size set.
-            bitmap = screenshoter.screenshot(crop, width, height, 0, 1,
-                    inRotation, rot);
+            final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot);
             if (bitmap == null) {
                 Slog.w(TAG_WM, "Failed to take screenshot");
                 return null;
             }
+
+            // Create a copy of the screenshot that is immutable and backed in ashmem.
+            // This greatly reduces the overhead of passing the bitmap between processes.
+            final Bitmap ret = bitmap.createAshmemBitmap(config);
+            bitmap.recycle();
+            return ret;
         }
-        return bitmap;
+    }
+
+    private boolean shouldScreenshotWallpaper() {
+        MutableBoolean screenshotReady = new MutableBoolean(false);
+
+        forAllWindows(w -> {
+            if (!w.mIsWallpaper) {
+                return false;
+            }
+
+            // Found the wallpaper window
+            final WindowStateAnimator winAnim = w.mWinAnimator;
+
+            if (winAnim.getShown() && winAnim.mLastAlpha > 0f) {
+                screenshotReady.value = true;
+            }
+
+            return true;
+        }, true /* traverseTopToBottom */);
+
+        return screenshotReady.value;
     }
 
     // TODO: Can this use createRotationMatrix()?
@@ -3746,11 +3559,39 @@
         }
     }
 
+    private final class AboveAppWindowContainers extends NonAppWindowContainers {
+        AboveAppWindowContainers(String name) {
+            super(name);
+        }
+
+        void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
+            boolean needAssignIme = imeContainer != null
+                    && imeContainer.getSurfaceControl() != null;
+            for (int j = 0; j < mChildren.size(); ++j) {
+                final WindowToken wt = mChildren.get(j);
+                wt.assignLayer(t, j);
+                wt.assignChildLayers(t);
+
+                int layer = mService.mPolicy.getWindowLayerFromTypeLw(
+                        wt.windowType, wt.mOwnerCanManageAppTokens);
+                if (needAssignIme && layer >= TYPE_INPUT_METHOD_DIALOG) {
+                    t.setRelativeLayer(imeContainer.getSurfaceControl(),
+                            wt.getSurfaceControl(), -1);
+                    needAssignIme = false;
+                }
+            }
+            if (needAssignIme) {
+                t.setRelativeLayer(imeContainer.getSurfaceControl(),
+                        getSurfaceControl(), Integer.MIN_VALUE);
+            }
+        }
+    }
+
     /**
      * Window container class that contains all containers on this display that are not related to
      * Apps. E.g. status bar.
      */
-    private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+    private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
         /**
          * Compares two child window tokens returns -1 if the first is lesser than the second in
          * terms of z-order and 1 otherwise.
@@ -3819,15 +3660,6 @@
         }
     }
 
-    /**
-     * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
-     */
-    @FunctionalInterface
-    private interface Screenshoter<E> {
-        E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
-                boolean useIdentityTransform, int rotation);
-    }
-
     SurfaceControl.Builder makeSurface(SurfaceSession s) {
         return mService.makeSurfaceBuilder(s)
                 .setParent(mWindowingLayer);
@@ -3848,12 +3680,8 @@
             return b;
         }
 
-        b.setName(child.getName());
-        if (child.isScreenOverlay()) {
-            return b.setParent(mOverlayLayer);
-        } else {
-            return b.setParent(mWindowingLayer);
-        }
+        return b.setName(child.getName())
+                .setParent(mWindowingLayer);
     }
 
     /**
@@ -3891,26 +3719,36 @@
         mAboveAppWindowsContainers.assignLayer(t, 2);
 
         WindowState imeTarget = mService.mInputMethodTarget;
-        if (imeTarget == null || imeTarget.inSplitScreenWindowingMode()) {
-            // In split-screen windowing mode we can't layer the
-            // IME relative to the IME target because it needs to
-            // go over the docked divider, so instead we place it on top
-            // of everything and use relative layering of windows which need
-            // to go above it (see special logic in WindowState#assignLayer)
-            mImeWindowsContainers.assignLayer(t, 3);
-        } else {
+        boolean needAssignIme = true;
+
+        // In the case where we have an IME target that is not in split-screen
+        // mode IME assignment is easy. We just need the IME to go directly above
+        // the target. This way children of the target will naturally go above the IME
+        // and everyone is happy.
+        //
+        // In the case of split-screen windowing mode, we need to elevate the IME above the
+        // docked divider while keeping the app itself below the docked divider, so instead
+        // we use relative layering of the IME targets child windows, and place the
+        // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}).
+        //
+        // In the case where we have no IME target we assign it where it's base layer would
+        // place it in the AboveAppWindowContainers.
+        if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode()
+                && (imeTarget.getSurfaceControl() != null)) {
             t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
                     imeTarget.getSurfaceControl(),
                     // TODO: We need to use an extra level on the app surface to ensure
                     // this is always above SurfaceView but always below attached window.
                     1);
+            needAssignIme = false;
         }
 
         // Above we have assigned layers to our children, now we ask them to assign
         // layers to their children.
         mBelowAppWindowsContainers.assignChildLayers(t);
         mTaskStackContainers.assignChildLayers(t);
-        mAboveAppWindowsContainers.assignChildLayers(t);
+        mAboveAppWindowsContainers.assignChildLayers(t,
+                needAssignIme == true ? mImeWindowsContainers : null);
         mImeWindowsContainers.assignChildLayers(t);
     }
 
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index e81d366..112e62f 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -645,15 +645,15 @@
             try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                 transaction.setPosition(
                         mSurfaceControl,
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y));
                 transaction.setAlpha(
                         mSurfaceControl,
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
                 transaction.setMatrix(
                         mSurfaceControl,
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
-                        0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+                        0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
                 transaction.apply();
             }
         }
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index c2a4be5..e7547bf 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -87,12 +87,6 @@
         }
     }
 
-    public boolean isVisible() {
-        synchronized (mWindowMap) {
-            return mContainer != null && mContainer.isVisible();
-        }
-    }
-
     public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 5d4ba09..87d0a40 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -382,6 +382,27 @@
         mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
         mWindowOriginalBounds.set(startBounds);
 
+        // Notify the app that resizing has started, even though we haven't received any new
+        // bounds yet. This will guarantee that the app starts the backdrop renderer before
+        // configuration changes which could cause an activity restart.
+        if (mResizing) {
+            synchronized (mService.mWindowMap) {
+                notifyMoveLocked(startX, startY);
+            }
+
+            // Perform the resize on the WMS handler thread when we don't have the WMS lock held
+            // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver
+            // callbacks are delivered on the same handler so this initial resize is always
+            // guaranteed to happen before subsequent drag resizes.
+            mService.mH.post(() -> {
+                try {
+                    mService.mActivityManager.resizeTask(
+                            mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
+                } catch (RemoteException e) {
+                }
+            });
+        }
+
         // Make sure we always have valid drag bounds even if the drag ends before any move events
         // have been handled.
         mWindowDragBounds.set(startBounds);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index e644417..84e475a 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,12 +18,12 @@
 
 import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
 import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -35,6 +35,7 @@
 import android.util.Slog;
 import android.view.DisplayListCanvas;
 import android.view.RenderNode;
+import android.view.SurfaceControl;
 import android.view.ThreadedRenderer;
 import android.view.WindowManager.LayoutParams;
 
@@ -210,11 +211,28 @@
         if (mainWindow == null) {
             return null;
         }
+        if (!mService.mPolicy.isScreenOn()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+            }
+            return null;
+        }
+        if (task.getSurfaceControl() == null) {
+            return null;
+        }
+
         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
         final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
-        final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
-                -1, -1, false, scaleFraction, false, true);
+        final Rect taskFrame = new Rect();
+        task.getBounds(taskFrame);
+
+        final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+                task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+
         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot");
+            }
             return null;
         }
         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 610453e..6467582 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -731,29 +731,8 @@
         final WindowContainer p = getParent();
         // Give the parent a chance to set properties. In hierarchy v1 we rely
         // on this to set full-screen dimensions on all our Surface-less Layers.
-        final SurfaceControl.Builder b = p.makeChildSurface(child);
-        if (child != null && child.isScreenOverlay()) {
-            // If it's a screen overlay it's been promoted in the hierarchy (wrt to the
-            // WindowContainer hierarchy vs the SurfaceControl hierarchy)
-            // and we shouldn't set ourselves as the parent.
-            return b;
-        } else {
-            return b.setParent(mSurfaceControl);
-        }
-    }
-
-    /**
-     * There are various layers which require promotion from the WindowContainer
-     * hierarchy to the Overlay layer described in {@link DisplayContent}. See {@link WindowState}
-     * for the particular usage.
-     *
-     * TODO: Perhaps this should be eliminated, either through modifying
-     * the window container hierarchy or through modifying the way we express these overlay
-     * Surfaces (for example, the Magnification Overlay could be implemented like the Strict-mode
-     * Flash and not actually use a WindowState).
-     */
-    boolean isScreenOverlay() {
-        return false;
+        return p.makeChildSurface(child)
+                .setParent(mSurfaceControl);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1cd4080..82f842c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3691,9 +3691,8 @@
         }
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
-            return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
-                    -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
-                    Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
+            return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+                    true /* wallpaperOnly */);
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -3712,10 +3711,8 @@
         }
 
         FgThread.getHandler().post(() -> {
-            Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
-                    -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
-                    1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
-                    false /* includeDecor */);
+            Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+                    false /* wallpaperOnly */);
             try {
                 receiver.onHandleAssistScreenshot(bm);
             } catch (RemoteException e) {
@@ -3749,29 +3746,21 @@
      * In portrait mode, it grabs the full screenshot.
      *
      * @param displayId the Display to take a screenshot of.
-     * @param width the width of the target bitmap
-     * @param height the height of the target bitmap
-     * @param includeFullDisplay true if the screen should not be cropped before capture
-     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
      * @param config of the output bitmap
      * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
-     * @param includeDecor whether to include window decors, like the status or navigation bar
-     *                     background of the window
      */
-    private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
-            int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
-            boolean wallpaperOnly, boolean includeDecor) {
+    private Bitmap screenshotApplications(int displayId, Bitmap.Config config,
+            boolean wallpaperOnly) {
         final DisplayContent displayContent;
         synchronized(mWindowMap) {
             displayContent = mRoot.getDisplayContentOrCreate(displayId);
             if (displayContent == null) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                        + ": returning null. No Display for displayId=" + displayId);
+                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for "
+                        + "displayId=" + displayId);
                 return null;
             }
         }
-        return displayContent.screenshotApplications(appToken, width, height,
-                includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
+        return displayContent.screenshotDisplay(config, wallpaperOnly);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 90e9c23..c2ac905 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -58,6 +58,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -4110,9 +4111,6 @@
             policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top,
                     displayInfo.logicalWidth - mCompatFrame.left,
                     displayInfo.logicalHeight - mCompatFrame.top);
-        } else if (mLayer >= mService.mSystemDecorLayer) {
-            // Above the decor layer is easy, just use the entire window
-            policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
         } else if (mDecorFrame.isEmpty()) {
             // Windows without policy decor aren't cropped.
             policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
@@ -4346,27 +4344,19 @@
     @Override
     boolean shouldMagnify() {
         if (mAttrs.type == TYPE_INPUT_METHOD ||
-                mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
-            return false;
-        } else if (isScreenOverlay()) {
+                mAttrs.type == TYPE_INPUT_METHOD_DIALOG ||
+                mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
+                mAttrs.type == TYPE_NAVIGATION_BAR ||
+                // It's tempting to wonder: Have we forgotten the rounded corners overlay?
+                // worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL
+                mAttrs.type == TYPE_NAVIGATION_BAR_PANEL ||
+                mAttrs.type == TYPE_STATUS_BAR) {
             return false;
         }
         return true;
     }
 
     @Override
-    boolean isScreenOverlay() {
-        // It's tempting to wonder: Have we forgotten the rounded corners overlay?
-        // worry not: it's a fake TYPE_NAVIGATION_BAR.
-        if (mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
-                mAttrs.type == TYPE_NAVIGATION_BAR ||
-                mAttrs.type == TYPE_STATUS_BAR) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     SurfaceSession getSession() {
         if (mSession.mSurfaceSession != null) {
             return mSession.mSurfaceSession;
@@ -4382,7 +4372,7 @@
 
     @Override
     SurfaceControl.Builder makeSurface() {
-        return mToken.makeChildSurface(this);
+        return getParent().makeChildSurface(this);
     }
 
     private void applyDims(Dimmer dimmer) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d2b3748..4b5661f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -871,11 +871,9 @@
 
     void computeShownFrameLocked() {
         final boolean selfTransformation = mHasLocalTransformation;
-        Transformation attachedTransformation =
-                (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
-                ? mParentWinAnimator.mTransformation : null;
         Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
                 ? mAppAnimator.transformation : null;
+        Transformation wallpaperTargetTransformation = null;
 
         // Wallpapers are animated based on the "real" window they
         // are currently targeting.
@@ -885,9 +883,9 @@
             if (wallpaperAnimator.mHasLocalTransformation &&
                     wallpaperAnimator.mAnimation != null &&
                     !wallpaperAnimator.mAnimation.getDetachWallpaper()) {
-                attachedTransformation = wallpaperAnimator.mTransformation;
-                if (DEBUG_WALLPAPER && attachedTransformation != null) {
-                    Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+                wallpaperTargetTransformation = wallpaperAnimator.mTransformation;
+                if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) {
+                    Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation);
                 }
             }
             final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
@@ -909,7 +907,7 @@
                 screenRotationAnimation != null && screenRotationAnimation.isAnimating();
 
         mHasClipRect = false;
-        if (selfTransformation || attachedTransformation != null
+        if (selfTransformation || wallpaperTargetTransformation != null
                 || appTransformation != null || screenAnimation) {
             // cache often used attributes locally
             final Rect frame = mWin.mFrame;
@@ -939,18 +937,27 @@
             if (selfTransformation) {
                 tmpMatrix.postConcat(mTransformation.getMatrix());
             }
-            if (attachedTransformation != null) {
-                tmpMatrix.postConcat(attachedTransformation.getMatrix());
+
+            if (wallpaperTargetTransformation != null) {
+                tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix());
             }
             if (appTransformation != null) {
                 tmpMatrix.postConcat(appTransformation.getMatrix());
             }
 
+            int left = frame.left;
+            int top = frame.top;
+            if (mWin.isChildWindow()) {
+                WindowState parent = mWin.getParentWindow();
+                left -= parent.mFrame.left;
+                top  -= parent.mFrame.top;
+            }
+
             // The translation that applies the position of the window needs to be applied at the
             // end in case that other translations include scaling. Otherwise the scaling will
             // affect this translation. But it needs to be set before the screen rotation animation
             // so the pivot point is at the center of the screen for all windows.
-            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+            tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset);
             if (screenAnimation) {
                 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
             }
@@ -985,8 +992,8 @@
                 if (selfTransformation) {
                     mShownAlpha *= mTransformation.getAlpha();
                 }
-                if (attachedTransformation != null) {
-                    mShownAlpha *= attachedTransformation.getAlpha();
+                if (wallpaperTargetTransformation != null) {
+                    mShownAlpha *= wallpaperTargetTransformation.getAlpha();
                 }
                 if (appTransformation != null) {
                     mShownAlpha *= appTransformation.getAlpha();
@@ -1017,8 +1024,8 @@
                     && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
                     TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
                     + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
-                    + " attached=" + (attachedTransformation == null ?
-                            "null" : attachedTransformation.getAlpha())
+                    + " attached=" + (wallpaperTargetTransformation == null ?
+                            "null" : wallpaperTargetTransformation.getAlpha())
                     + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
                     + " screen=" + (screenAnimation ?
                             screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 4a2da37..8b9cf4b 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -94,6 +94,7 @@
     android.hardware.broadcastradio@1.2 \
     android.hardware.contexthub@1.0 \
     android.hardware.gnss@1.0 \
+    android.hardware.gnss@1.1 \
     android.hardware.ir@1.0 \
     android.hardware.light@2.0 \
     android.hardware.power@1.0 \
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index df85e31..35e65bc 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -17,16 +17,18 @@
 #define LOG_TAG "UsbHostManagerJNI"
 #include "utils/Log.h"
 
+#include <stdlib.h>
+
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
 #include <usbhost/usbhost.h>
 
-#define MAX_DESCRIPTORS_LENGTH 16384
+#define MAX_DESCRIPTORS_LENGTH 4096
 
 // com.android.server.usb.descriptors
 extern "C" {
-jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors(
+jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors_1native(
         JNIEnv* env, jobject thiz, jstring deviceAddr) {
     const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL);
     struct usb_device* device = usb_device_open(deviceAddrStr);
@@ -39,6 +41,7 @@
 
     int fd = usb_device_get_fd(device);
     if (fd < 0) {
+        usb_device_close(device);
         return NULL;
     }
 
@@ -46,17 +49,48 @@
     jbyte buffer[MAX_DESCRIPTORS_LENGTH];
     lseek(fd, 0, SEEK_SET);
     int numBytes = read(fd, buffer, sizeof(buffer));
-
+    jbyteArray ret = NULL;
     usb_device_close(device);
 
-    jbyteArray ret = NULL;
-    if (numBytes != 0) {
+    if (numBytes > 0) {
         ret = env->NewByteArray(numBytes);
         env->SetByteArrayRegion(ret, 0, numBytes, buffer);
+    } else {
+        ALOGE("error reading descriptors\n");
     }
+
     return ret;
 }
 
+jstring JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getDescriptorString_1native(
+        JNIEnv* env, jobject thiz, jstring deviceAddr, jint stringId) {
+
+    const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL);
+    struct usb_device* device = usb_device_open(deviceAddrStr);
+    env->ReleaseStringUTFChars(deviceAddr, deviceAddrStr);
+
+    if (!device) {
+        ALOGE("usb_device_open failed");
+        return NULL;
+    }
+
+    int fd = usb_device_get_fd(device);
+    if (fd < 0) {
+        ALOGE("usb_device_get_fd failed");
+        usb_device_close(device);
+        return NULL;
+    }
+
+    char* c_str = usb_device_get_string(device, stringId, 0 /*timeout*/);
+
+    jstring j_str = env->NewStringUTF(c_str);
+
+    free(c_str);
+    usb_device_close(device);
+
+    return j_str;
+}
+
 } // extern "C"
 
 
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 88ae824..24f2014 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -22,8 +22,6 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 
-#include <usbhost/usbhost.h>
-
 #include <stdio.h>
 #include <asm/byteorder.h>
 #include <sys/types.h>
@@ -31,22 +29,20 @@
 #include <fcntl.h>
 #include <sys/ioctl.h>
 
+#include <usbhost/usbhost.h>
+
+#define MAX_DESCRIPTORS_LENGTH 4096
+
 namespace android
 {
 
-static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
-
 static struct parcel_file_descriptor_offsets_t
 {
     jclass mClass;
     jmethodID mConstructor;
 } gParcelFileDescriptorOffsets;
 
-static jmethodID method_beginUsbDeviceAdded;
-static jmethodID method_addUsbConfiguration;
-static jmethodID method_addUsbInterface;
-static jmethodID method_addUsbEndpoint;
-static jmethodID method_endUsbDeviceAdded;
+static jmethodID method_usbDeviceAdded;
 static jmethodID method_usbDeviceRemoved;
 
 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
@@ -57,101 +53,52 @@
     }
 }
 
-static int usb_device_added(const char *devname, void* client_data) {
-    struct usb_descriptor_header* desc;
-    struct usb_descriptor_iter iter;
-
-    struct usb_device *device = usb_device_open(devname);
+static int usb_device_added(const char *devAddress, void* clientData) {
+    struct usb_device *device = usb_device_open(devAddress);
     if (!device) {
         ALOGE("usb_device_open failed\n");
         return 0;
     }
 
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jobject thiz = (jobject)client_data;
     const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device);
+    int classID = deviceDesc->bDeviceClass;
+    int subClassID = deviceDesc->bDeviceSubClass;
 
-    char *manufacturer = usb_device_get_manufacturer_name(device,
-            USB_CONTROL_TRANSFER_TIMEOUT_MS);
-    char *product = usb_device_get_product_name(device,
-            USB_CONTROL_TRANSFER_TIMEOUT_MS);
-    int version = usb_device_get_version(device);
-    char *serial = usb_device_get_serial(device,
-            USB_CONTROL_TRANSFER_TIMEOUT_MS);
+    // get the raw descriptors
+    int numBytes = usb_device_get_descriptors_length(device);
+    if (numBytes > 0) {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        jobject thiz = (jobject)clientData;
+        jstring deviceAddress = env->NewStringUTF(devAddress);
 
-    jstring deviceName = env->NewStringUTF(devname);
-    jstring manufacturerName = AndroidRuntime::NewStringLatin1(env, manufacturer);
-    jstring productName = AndroidRuntime::NewStringLatin1(env, product);
-    jstring serialNumber = AndroidRuntime::NewStringLatin1(env, serial);
+        jbyteArray descriptorsArray = env->NewByteArray(numBytes);
+        const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device);
+        env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors);
 
-    jboolean result = env->CallBooleanMethod(thiz, method_beginUsbDeviceAdded,
-            deviceName, usb_device_get_vendor_id(device), usb_device_get_product_id(device),
-            deviceDesc->bDeviceClass, deviceDesc->bDeviceSubClass, deviceDesc->bDeviceProtocol,
-            manufacturerName, productName, version, serialNumber);
+        env->CallBooleanMethod(thiz, method_usbDeviceAdded,
+                deviceAddress, classID, subClassID, descriptorsArray);
 
-    env->DeleteLocalRef(serialNumber);
-    env->DeleteLocalRef(productName);
-    env->DeleteLocalRef(manufacturerName);
-    env->DeleteLocalRef(deviceName);
-    free(manufacturer);
-    free(product);
-    free(serial);
+        env->DeleteLocalRef(descriptorsArray);
+        env->DeleteLocalRef(deviceAddress);
 
-    if (!result) goto fail;
-
-    usb_descriptor_iter_init(device, &iter);
-
-    while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
-        if (desc->bDescriptorType == USB_DT_CONFIG) {
-            struct usb_config_descriptor *config = (struct usb_config_descriptor *)desc;
-            char *name = usb_device_get_string(device, config->iConfiguration,
-                    USB_CONTROL_TRANSFER_TIMEOUT_MS);
-            jstring configName = AndroidRuntime::NewStringLatin1(env, name);
-
-            env->CallVoidMethod(thiz, method_addUsbConfiguration,
-                    config->bConfigurationValue, configName, config->bmAttributes,
-                    config->bMaxPower);
-
-            env->DeleteLocalRef(configName);
-            free(name);
-        } else if (desc->bDescriptorType == USB_DT_INTERFACE) {
-            struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
-            char *name = usb_device_get_string(device, interface->iInterface,
-                    USB_CONTROL_TRANSFER_TIMEOUT_MS);
-            jstring interfaceName = AndroidRuntime::NewStringLatin1(env, name);
-
-            env->CallVoidMethod(thiz, method_addUsbInterface,
-                    interface->bInterfaceNumber, interfaceName, interface->bAlternateSetting,
-                    interface->bInterfaceClass, interface->bInterfaceSubClass,
-                    interface->bInterfaceProtocol);
-
-            env->DeleteLocalRef(interfaceName);
-            free(name);
-        } else if (desc->bDescriptorType == USB_DT_ENDPOINT) {
-            struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)desc;
-
-            env->CallVoidMethod(thiz, method_addUsbEndpoint,
-                    endpoint->bEndpointAddress, endpoint->bmAttributes,
-                    __le16_to_cpu(endpoint->wMaxPacketSize), endpoint->bInterval);
-        }
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    } else {
+        // TODO return an error code here?
+        ALOGE("error reading descriptors\n");
     }
 
-    env->CallVoidMethod(thiz, method_endUsbDeviceAdded);
-
-fail:
     usb_device_close(device);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
 
     return 0;
 }
 
-static int usb_device_removed(const char *devname, void* client_data) {
+static int usb_device_removed(const char *devAddress, void* clientData) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jobject thiz = (jobject)client_data;
+    jobject thiz = (jobject)clientData;
 
-    jstring deviceName = env->NewStringUTF(devname);
-    env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceName);
-    env->DeleteLocalRef(deviceName);
+    jstring deviceAddress = env->NewStringUTF(devAddress);
+    env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceAddress);
+    env->DeleteLocalRef(deviceAddress);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return 0;
 }
@@ -168,11 +115,11 @@
 }
 
 static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */,
-                                                        jstring deviceName)
+                                                        jstring deviceAddress)
 {
-    const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);
-    struct usb_device* device = usb_device_open(deviceNameStr);
-    env->ReleaseStringUTFChars(deviceName, deviceNameStr);
+    const char *deviceAddressStr = env->GetStringUTFChars(deviceAddress, NULL);
+    struct usb_device* device = usb_device_open(deviceAddressStr);
+    env->ReleaseStringUTFChars(deviceAddress, deviceAddressStr);
 
     if (!device)
         return NULL;
@@ -206,34 +153,12 @@
         ALOGE("Can't find com/android/server/usb/UsbHostManager");
         return -1;
     }
-    method_beginUsbDeviceAdded = env->GetMethodID(clazz, "beginUsbDeviceAdded",
-            "(Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;ILjava/lang/String;)Z");
-    if (method_beginUsbDeviceAdded == NULL) {
+    method_usbDeviceAdded =
+            env->GetMethodID(clazz, "usbDeviceAdded", "(Ljava/lang/String;II[B)Z");
+    if (method_usbDeviceAdded == NULL) {
         ALOGE("Can't find beginUsbDeviceAdded");
         return -1;
     }
-    method_addUsbConfiguration = env->GetMethodID(clazz, "addUsbConfiguration",
-            "(ILjava/lang/String;II)V");
-    if (method_addUsbConfiguration == NULL) {
-        ALOGE("Can't find addUsbConfiguration");
-        return -1;
-    }
-    method_addUsbInterface = env->GetMethodID(clazz, "addUsbInterface",
-            "(ILjava/lang/String;IIII)V");
-    if (method_addUsbInterface == NULL) {
-        ALOGE("Can't find addUsbInterface");
-        return -1;
-    }
-    method_addUsbEndpoint = env->GetMethodID(clazz, "addUsbEndpoint", "(IIII)V");
-    if (method_addUsbEndpoint == NULL) {
-        ALOGE("Can't find addUsbEndpoint");
-        return -1;
-    }
-    method_endUsbDeviceAdded = env->GetMethodID(clazz, "endUsbDeviceAdded", "()V");
-    if (method_endUsbDeviceAdded == NULL) {
-        ALOGE("Can't find endUsbDeviceAdded");
-        return -1;
-    }
     method_usbDeviceRemoved = env->GetMethodID(clazz, "usbDeviceRemoved",
             "(Ljava/lang/String;)V");
     if (method_usbDeviceRemoved == NULL) {
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index daf3f2f..4cbe64d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -18,7 +18,7 @@
 
 #define LOG_NDEBUG 0
 
-#include <android/hardware/gnss/1.0/IGnss.h>
+#include <android/hardware/gnss/1.1/IGnss.h>
 
 #include <nativehelper/JNIHelp.h>
 #include "jni.h"
@@ -78,6 +78,9 @@
 using android::hardware::hidl_death_recipient;
 using android::hidl::base::V1_0::IBase;
 
+using android::hardware::gnss::V1_0::GnssLocation;
+using android::hardware::gnss::V1_0::GnssLocationFlags;
+
 using android::hardware::gnss::V1_0::IAGnss;
 using android::hardware::gnss::V1_0::IAGnssCallback;
 using android::hardware::gnss::V1_0::IAGnssCallback;
@@ -86,7 +89,6 @@
 using android::hardware::gnss::V1_0::IGnss;
 using android::hardware::gnss::V1_0::IGnssBatching;
 using android::hardware::gnss::V1_0::IGnssBatchingCallback;
-using android::hardware::gnss::V1_0::IGnssCallback;
 using android::hardware::gnss::V1_0::IGnssConfiguration;
 using android::hardware::gnss::V1_0::IGnssDebug;
 using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -100,11 +102,14 @@
 using android::hardware::gnss::V1_0::IGnssXtra;
 using android::hardware::gnss::V1_0::IGnssXtraCallback;
 
+using IGnssV1_1 = android::hardware::gnss::V1_1::IGnss;
+using android::hardware::gnss::V1_1::IGnssCallback;
+
 struct GnssDeathRecipient : virtual public hidl_death_recipient
 {
     // hidl_death_recipient interface
     virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
-      // TODO(gomo): implement a better death recovery mechanism without
+      // TODO(b/37460011): implement a better death recovery mechanism without
       // crashing system server process as described in go//treble-gnss-death
       LOG_ALWAYS_FATAL("Abort due to IGNSS hidl service failure,"
             " restarting system server");
@@ -113,6 +118,7 @@
 
 sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
 sp<IGnss> gnssHal = nullptr;
+sp<IGnssV1_1> gnssHalV1_1 = nullptr;
 sp<IGnssXtra> gnssXtraIface = nullptr;
 sp<IAGnssRil> agnssRilIface = nullptr;
 sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
@@ -303,33 +309,33 @@
     return env;
 }
 
-static jobject translateLocation(JNIEnv* env, const hardware::gnss::V1_0::GnssLocation& location) {
+static jobject translateLocation(JNIEnv* env, const GnssLocation& location) {
     JavaObject object(env, "android/location/Location", "gps");
 
     uint16_t flags = static_cast<uint32_t>(location.gnssLocationFlags);
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) {
+    if (flags & GnssLocationFlags::HAS_LAT_LONG) {
         SET(Latitude, location.latitudeDegrees);
         SET(Longitude, location.longitudeDegrees);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_ALTITUDE) {
+    if (flags & GnssLocationFlags::HAS_ALTITUDE) {
         SET(Altitude, location.altitudeMeters);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED) {
+    if (flags & GnssLocationFlags::HAS_SPEED) {
         SET(Speed, location.speedMetersPerSec);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING) {
+    if (flags & GnssLocationFlags::HAS_BEARING) {
         SET(Bearing, location.bearingDegrees);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_HORIZONTAL_ACCURACY) {
+    if (flags & GnssLocationFlags::HAS_HORIZONTAL_ACCURACY) {
         SET(Accuracy, location.horizontalAccuracyMeters);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_VERTICAL_ACCURACY) {
+    if (flags & GnssLocationFlags::HAS_VERTICAL_ACCURACY) {
         SET(VerticalAccuracyMeters, location.verticalAccuracyMeters);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED_ACCURACY) {
+    if (flags & GnssLocationFlags::HAS_SPEED_ACCURACY) {
         SET(SpeedAccuracyMetersPerSecond, location.speedAccuracyMetersPerSecond);
     }
-    if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING_ACCURACY) {
+    if (flags & GnssLocationFlags::HAS_BEARING_ACCURACY) {
         SET(BearingAccuracyDegrees, location.bearingAccuracyDegrees);
     }
     SET(Time, location.timestamp);
@@ -341,8 +347,7 @@
  * GnssCallback class implements the callback methods for IGnss interface.
  */
 struct GnssCallback : public IGnssCallback {
-    Return<void> gnssLocationCb(
-          const android::hardware::gnss::V1_0::GnssLocation& location) override;
+    Return<void> gnssLocationCb(const GnssLocation& location) override;
     Return<void> gnssStatusCb(const IGnssCallback::GnssStatusValue status) override;
     Return<void> gnssSvStatusCb(const IGnssCallback::GnssSvStatus& svStatus) override;
     Return<void> gnssNmeaCb(int64_t timestamp, const android::hardware::hidl_string& nmea) override;
@@ -352,6 +357,9 @@
     Return<void> gnssRequestTimeCb() override;
     Return<void> gnssSetSystemInfoCb(const IGnssCallback::GnssSystemInfo& info) override;
 
+    // New in 1.1
+    Return<void> gnssNameCb(const android::hardware::hidl_string& name) override;
+
     static GnssSvInfo sGnssSvList[static_cast<uint32_t>(
             android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)];
     static size_t sGnssSvListSize;
@@ -360,19 +368,30 @@
     static size_t sNmeaStringLength;
 };
 
+Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) {
+    ALOGD("%s: name=%s\n", __func__, name.c_str());
+
+    // TODO(b/38003769): build Java code to connect to below code
+    /*
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareName, name);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    */
+    return Void();
+}
+
 IGnssCallback::GnssSvInfo GnssCallback::sGnssSvList[static_cast<uint32_t>(
         android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)];
 const char* GnssCallback::sNmeaString = nullptr;
 size_t GnssCallback::sNmeaStringLength = 0;
 size_t GnssCallback::sGnssSvListSize = 0;
 
-Return<void> GnssCallback::gnssLocationCb(
-        const ::android::hardware::gnss::V1_0::GnssLocation& location) {
+Return<void> GnssCallback::gnssLocationCb(const GnssLocation& location) {
     JNIEnv* env = getJniEnv();
 
     jobject jLocation = translateLocation(env, location);
     bool hasLatLong = (static_cast<uint32_t>(location.gnssLocationFlags) &
-            hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) != 0;
+            GnssLocationFlags::HAS_LAT_LONG) != 0;
 
     env->CallVoidMethod(mCallbacksObj,
                         method_reportLocation,
@@ -484,12 +503,12 @@
     // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
     Return<void> gnssGeofenceTransitionCb(
             int32_t geofenceId,
-            const android::hardware::gnss::V1_0::GnssLocation& location,
+            const GnssLocation& location,
             GeofenceTransition transition,
             hardware::gnss::V1_0::GnssUtcTime timestamp) override;
     Return<void> gnssGeofenceStatusCb(
             GeofenceAvailability status,
-            const android::hardware::gnss::V1_0::GnssLocation& location) override;
+            const GnssLocation& location) override;
     Return<void> gnssGeofenceAddCb(int32_t geofenceId,
                                    GeofenceStatus status) override;
     Return<void> gnssGeofenceRemoveCb(int32_t geofenceId,
@@ -502,7 +521,7 @@
 
 Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
         int32_t geofenceId,
-        const android::hardware::gnss::V1_0::GnssLocation& location,
+        const GnssLocation& location,
         GeofenceTransition transition,
         hardware::gnss::V1_0::GnssUtcTime timestamp) {
     JNIEnv* env = getJniEnv();
@@ -522,7 +541,7 @@
 
 Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(
         GeofenceAvailability status,
-        const android::hardware::gnss::V1_0::GnssLocation& location) {
+        const GnssLocation& location) {
     JNIEnv* env = getJniEnv();
 
     jobject jLocation = translateLocation(env, location);
@@ -976,12 +995,12 @@
     * follow.
     */
     Return<void> gnssLocationBatchCb(
-        const ::android::hardware::hidl_vec<hardware::gnss::V1_0::GnssLocation> & locations)
+        const ::android::hardware::hidl_vec<GnssLocation> & locations)
         override;
 };
 
 Return<void> GnssBatchingCallback::gnssLocationBatchCb(
-        const ::android::hardware::hidl_vec<hardware::gnss::V1_0::GnssLocation> & locations) {
+        const ::android::hardware::hidl_vec<GnssLocation> & locations) {
     JNIEnv* env = getJniEnv();
 
     jobjectArray jLocations = env->NewObjectArray(locations.size(),
@@ -1049,8 +1068,13 @@
         LOG_ALWAYS_FATAL("Unable to get Java VM. Error: %d", jvmStatus);
     }
 
-    // TODO(b/31632518)
-    gnssHal = IGnss::getService();
+    gnssHal = gnssHalV1_1 = IGnssV1_1::getService();
+
+    if (gnssHal == nullptr) {
+        ALOGD("gnssHal 1.1 was null, trying 1.0");
+        gnssHal = IGnss::getService();
+    }
+
     if (gnssHal != nullptr) {
       gnssHalDeathRecipient = new GnssDeathRecipient();
       hardware::Return<bool> linked = gnssHal->linkToDeath(
@@ -1160,7 +1184,6 @@
     if (!mCallbacksObj)
         mCallbacksObj = env->NewGlobalRef(obj);
 
-    sp<IGnssCallback> gnssCbIface = new GnssCallback();
     /*
      * Fail if the main interface fails to initialize
      */
@@ -1169,7 +1192,14 @@
         return JNI_FALSE;
     }
 
-    auto result = gnssHal->setCallback(gnssCbIface);
+    sp<IGnssCallback> gnssCbIface = new GnssCallback();
+
+    Return<bool> result = false;
+    if (gnssHalV1_1 != nullptr) {
+        result = gnssHalV1_1->setCallback_1_1(gnssCbIface);
+    } else {
+        result = gnssHal->setCallback(gnssCbIface);
+    }
     if (!result.isOk() || !result) {
         ALOGE("SetCallback for Gnss Interface fails\n");
         return JNI_FALSE;
@@ -1182,7 +1212,7 @@
         result = gnssXtraIface->setCallback(gnssXtraCbIface);
         if (!result.isOk() || !result) {
             gnssXtraIface = nullptr;
-            ALOGE("SetCallback for Gnss Xtra Interface fails\n");
+            ALOGI("SetCallback for Gnss Xtra Interface fails\n");
         }
     }
 
@@ -1190,21 +1220,21 @@
     if (agnssIface != nullptr) {
         agnssIface->setCallback(aGnssCbIface);
     } else {
-        ALOGE("Unable to Initialize AGnss interface\n");
+        ALOGI("Unable to Initialize AGnss interface\n");
     }
 
     sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
     if (gnssGeofencingIface != nullptr) {
       gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
     } else {
-        ALOGE("Unable to initialize GNSS Geofencing interface\n");
+        ALOGI("Unable to initialize GNSS Geofencing interface\n");
     }
 
     sp<IGnssNiCallback> gnssNiCbIface = new GnssNiCallback();
     if (gnssNiIface != nullptr) {
         gnssNiIface->setCallback(gnssNiCbIface);
     } else {
-        ALOGE("Unable to initialize GNSS NI interface\n");
+        ALOGI("Unable to initialize GNSS NI interface\n");
     }
 
     sp<IAGnssRilCallback> aGnssRilCbIface = new AGnssRilCallback();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 6086354..4a6bee5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -64,6 +64,8 @@
     private final DevicePolicyManagerService mDpm;
     private final AlarmManager mAlarmManager;
 
+    private long mId;
+
     private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
         @Override
         public void onAlarm() {
@@ -185,6 +187,10 @@
     private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
         Bundle notificationExtras = null;
         if (mNetworkEvents.size() > 0) {
+            // Assign ids to the events.
+            for (NetworkEvent event : mNetworkEvents) {
+                event.setId(mId++);
+            }
             // Finalize the batch and start a new one from scratch.
             if (mBatches.size() >= MAX_BATCHES) {
                 // Remove the oldest batch if we hit the limit.
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index a2ec234..e5ab44f 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -26,6 +26,7 @@
     platform-test-annotations \
     ShortcutManagerTestUtils \
     truth-prebuilt \
+    testables \
     testng
 
 LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 9a5ebed..0499bf0 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -58,6 +58,7 @@
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
new file mode 100644
index 0000000..918807d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import static android.app.AlarmManager.RTC;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.util.ObjectUtils;
+import com.android.server.AlarmManagerService.Alarm;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AlarmManagerServiceTest {
+    private SparseArray<ArrayList<Alarm>> addPendingAlarm(
+            SparseArray<ArrayList<Alarm>> all, int uid, String name, boolean removeIt) {
+        ArrayList<Alarm> uidAlarms = all.get(uid);
+        if (uidAlarms == null) {
+            all.put(uid, uidAlarms = new ArrayList<>());
+        }
+        // Details don't matter.
+        uidAlarms.add(new Alarm(
+                removeIt ? RTC : RTC_WAKEUP,
+                0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+        return all;
+    }
+
+    private static String toString(SparseArray<ArrayList<Alarm>> pendingAlarms) {
+        final StringBuilder sb = new StringBuilder();
+
+        String sep = "";
+        for (int i = 0; i < pendingAlarms.size(); i++) {
+            sb.append(sep);
+            sep = ", ";
+            sb.append("[");
+            sb.append(pendingAlarms.keyAt(i));
+            sb.append(": ");
+            sb.append(toString(pendingAlarms.valueAt(i)));
+            sb.append("]");
+        }
+        return sb.toString();
+    }
+
+    private static String toString(ArrayList<Alarm> alarms) {
+        final StringBuilder sb = new StringBuilder();
+
+        alarms.sort((a, b) -> ObjectUtils.compare(a.packageName, b.packageName));
+
+        String sep = "";
+        for (Alarm a : alarms) {
+            sb.append(sep);
+            sep = ", ";
+            sb.append(a.packageName);
+        }
+        return sb.toString();
+    }
+
+    private void runCheckAllPendingAlarms(
+            SparseArray<ArrayList<Alarm>> pending, ArrayList<Alarm> alarmsToDeliver) {
+        // RTC_WAKEUP alarms are restricted.
+        AlarmManagerService.findAllUnrestrictedPendingBackgroundAlarmsLockedInner(pending,
+                alarmsToDeliver, alarm -> alarm.type == RTC_WAKEUP);
+    }
+
+    @Test
+    public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_empty() {
+        SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+        final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+
+        runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+        assertEquals("", toString(pending));
+        assertEquals("", toString(alarmsToDeliver));
+    }
+
+    @Test
+    public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_remove() {
+        SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+        addPendingAlarm(pending, 100001, "a1", false);
+
+        final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+
+        runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+        assertEquals("[100001: a1]", toString(pending));
+        assertEquals("", toString(alarmsToDeliver));
+    }
+
+    @Test
+    public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_nonremove() {
+        SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+        addPendingAlarm(pending, 100001, "a1", true);
+
+        final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+        runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+        assertEquals("", toString(pending));
+        assertEquals("a1", toString(alarmsToDeliver));
+    }
+
+    @Test
+    public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex() {
+        SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+        addPendingAlarm(pending, 100001, "a11", false);
+        addPendingAlarm(pending, 100001, "a12", true);
+        addPendingAlarm(pending, 100001, "a13", false);
+        addPendingAlarm(pending, 100001, "a14", true);
+
+        addPendingAlarm(pending, 100002, "a21", false);
+
+        addPendingAlarm(pending, 100003, "a31", true);
+
+        addPendingAlarm(pending, 100004, "a41", false);
+        addPendingAlarm(pending, 100004, "a42", false);
+
+        addPendingAlarm(pending, 100005, "a51", true);
+        addPendingAlarm(pending, 100005, "a52", true);
+
+        addPendingAlarm(pending, 100006, "a61", true);
+        addPendingAlarm(pending, 100006, "a62", false);
+        addPendingAlarm(pending, 100006, "a63", true);
+        addPendingAlarm(pending, 100006, "a64", false);
+
+        final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+        runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+        assertEquals("[100001: a11, a13], [100002: a21], [100004: a41, a42], [100006: a62, a64]",
+                toString(pending));
+        assertEquals("a12, a14, a31, a51, a52, a61, a63", toString(alarmsToDeliver));
+    }
+
+    @Test
+    public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex_allRemove() {
+        SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+        addPendingAlarm(pending, 100001, "a11", true);
+        addPendingAlarm(pending, 100001, "a12", true);
+        addPendingAlarm(pending, 100001, "a13", true);
+        addPendingAlarm(pending, 100001, "a14", true);
+
+        addPendingAlarm(pending, 100002, "a21", true);
+
+        addPendingAlarm(pending, 100003, "a31", true);
+
+        addPendingAlarm(pending, 100004, "a41", true);
+        addPendingAlarm(pending, 100004, "a42", true);
+
+        addPendingAlarm(pending, 100005, "a51", true);
+        addPendingAlarm(pending, 100005, "a52", true);
+
+        addPendingAlarm(pending, 100006, "a61", true);
+        addPendingAlarm(pending, 100006, "a62", true);
+        addPendingAlarm(pending, 100006, "a63", true);
+        addPendingAlarm(pending, 100006, "a64", true);
+
+        final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+        runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+        assertEquals("", toString(pending));
+        assertEquals("a11, a12, a13, a14, a21, a31, a41, a42, a51, a52, a61, a62, a63, a64",
+                toString(alarmsToDeliver));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
new file mode 100644
index 0000000..66d0da1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -0,0 +1,900 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import static com.android.server.ForceAppStandbyTracker.TARGET_OP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.server.ForceAppStandbyTracker.Listener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ForceAppStandbyTrackerTest {
+
+    private class ForceAppStandbyTrackerTestable extends ForceAppStandbyTracker {
+        ForceAppStandbyTrackerTestable() {
+            super(mMockContext, Looper.getMainLooper());
+        }
+
+        @Override
+        AppOpsManager injectAppOpsManager() {
+            return mMockAppOpsManager;
+        }
+
+        @Override
+        IAppOpsService injectIAppOpsService() {
+            return mMockIAppOpsService;
+        }
+
+        @Override
+        IActivityManager injectIActivityManager() {
+            return mMockIActivityManager;
+        }
+
+        @Override
+        PowerManagerInternal injectPowerManagerInternal() {
+            return mMockPowerManagerInternal;
+        }
+    }
+
+    private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1;
+    private static final int UID_2 = Process.FIRST_APPLICATION_UID + 2;
+    private static final int UID_3 = Process.FIRST_APPLICATION_UID + 3;
+    private static final int UID_10_1 = UserHandle.getUid(10, UID_1);
+    private static final int UID_10_2 = UserHandle.getUid(10, UID_2);
+    private static final int UID_10_3 = UserHandle.getUid(10, UID_3);
+    private static final String PACKAGE_1 = "package1";
+    private static final String PACKAGE_2 = "package2";
+    private static final String PACKAGE_3 = "package3";
+    private static final String PACKAGE_SYSTEM = "android";
+
+    private Handler mMainHandler;
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private IActivityManager mMockIActivityManager;
+
+    @Mock
+    private AppOpsManager mMockAppOpsManager;
+
+    @Mock
+    private IAppOpsService mMockIAppOpsService;
+
+    @Mock
+    private PowerManagerInternal mMockPowerManagerInternal;
+
+    private IUidObserver mIUidObserver;
+    private IAppOpsCallback.Stub mAppOpsCallback;
+    private Consumer<PowerSaveState> mPowerSaveObserver;
+    private BroadcastReceiver mReceiver;
+
+    private boolean mPowerSaveMode;
+
+    private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet();
+
+    @Before
+    public void setUp() {
+        mMainHandler = new Handler(Looper.getMainLooper());
+    }
+
+    private void waitUntilMainHandlerDrain() throws Exception {
+        final CountDownLatch l = new CountDownLatch(1);
+        mMainHandler.post(() -> {
+            l.countDown();
+        });
+        assertTrue(l.await(5, TimeUnit.SECONDS));
+    }
+
+    private PowerSaveState getPowerSaveState() {
+        return new PowerSaveState.Builder().setBatterySaverEnabled(mPowerSaveMode).build();
+    }
+
+    private ForceAppStandbyTrackerTestable newInstance() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString()))
+                .thenAnswer(inv -> {
+                    return mRestrictedPackages.indexOf(
+                            Pair.create(inv.getArgument(1), inv.getArgument(2))) >= 0 ?
+                            AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
+                });
+
+        final ForceAppStandbyTrackerTestable instance = new ForceAppStandbyTrackerTestable();
+
+        return instance;
+    }
+
+    private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
+
+        // Set up functions that start() calls.
+        when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
+                .thenAnswer(inv -> getPowerSaveState());
+        when(mMockAppOpsManager.getPackagesForOps(
+                any(int[].class)
+                )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
+
+        // Call start.
+        instance.start();
+
+        // Capture the listeners.
+        ArgumentCaptor<IUidObserver> uidObserverArgumentCaptor =
+                ArgumentCaptor.forClass(IUidObserver.class);
+        ArgumentCaptor<IAppOpsCallback.Stub> appOpsCallbackCaptor =
+                ArgumentCaptor.forClass(IAppOpsCallback.Stub.class);
+        ArgumentCaptor<Consumer<PowerSaveState>> powerSaveObserverCaptor =
+                ArgumentCaptor.forClass(Consumer.class);
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        verify(mMockIActivityManager).registerUidObserver(
+                uidObserverArgumentCaptor.capture(),
+                eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+                        | ActivityManager.UID_OBSERVER_ACTIVE),
+                eq(ActivityManager.PROCESS_STATE_UNKNOWN),
+                isNull());
+        verify(mMockIAppOpsService).startWatchingMode(
+                eq(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND),
+                isNull(),
+                appOpsCallbackCaptor.capture());
+        verify(mMockPowerManagerInternal).registerLowPowerModeObserver(
+                eq(ServiceType.FORCE_ALL_APPS_STANDBY),
+                powerSaveObserverCaptor.capture());
+
+        verify(mMockContext).registerReceiver(
+                receiverCaptor.capture(), any(IntentFilter.class));
+
+        mIUidObserver = uidObserverArgumentCaptor.getValue();
+        mAppOpsCallback = appOpsCallbackCaptor.getValue();
+        mPowerSaveObserver = powerSaveObserverCaptor.getValue();
+        mReceiver = receiverCaptor.getValue();
+
+        assertNotNull(mIUidObserver);
+        assertNotNull(mAppOpsCallback);
+        assertNotNull(mPowerSaveObserver);
+        assertNotNull(mReceiver);
+    }
+
+    private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException {
+        final Pair p = Pair.create(uid, packageName);
+        if (restrict) {
+            mRestrictedPackages.add(p);
+        } else {
+            mRestrictedPackages.remove(p);
+        }
+        if (mAppOpsCallback != null) {
+            mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
+        }
+    }
+
+    private static final int NONE = 0;
+    private static final int ALARMS_ONLY = 1 << 0;
+    private static final int JOBS_ONLY = 1 << 1;
+    private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
+
+    private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
+            int restrictionTypes) {
+        assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
+                instance.areJobsRestricted(uid, packageName));
+        assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
+                instance.areAlarmsRestricted(uid, packageName));
+    }
+
+    @Test
+    public void testAll() throws Exception {
+        final ForceAppStandbyTrackerTestable instance = newInstance();
+        callStart(instance);
+
+        assertFalse(instance.isForceAllAppsStandbyEnabled());
+        areRestricted(instance, UID_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, NONE);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        assertTrue(instance.isForceAllAppsStandbyEnabled());
+
+        areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        // Toggle the foreground state.
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        assertFalse(instance.isInForeground(UID_1));
+        assertFalse(instance.isInForeground(UID_2));
+        assertTrue(instance.isInForeground(Process.SYSTEM_UID));
+
+        mIUidObserver.onUidActive(UID_1);
+        areRestricted(instance, UID_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+        assertTrue(instance.isInForeground(UID_1));
+        assertFalse(instance.isInForeground(UID_2));
+
+        mIUidObserver.onUidGone(UID_1, /*disable=*/ false);
+        areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+        assertFalse(instance.isInForeground(UID_1));
+        assertFalse(instance.isInForeground(UID_2));
+
+        mIUidObserver.onUidActive(UID_1);
+        areRestricted(instance, UID_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        mIUidObserver.onUidIdle(UID_1, /*disable=*/ false);
+        areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+        assertFalse(instance.isInForeground(UID_1));
+        assertFalse(instance.isInForeground(UID_2));
+
+        // Toggle the app ops.
+        mPowerSaveMode = false;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+        areRestricted(instance, UID_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, NONE);
+        areRestricted(instance, UID_10_2, PACKAGE_2, NONE);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        setAppOps(UID_1, PACKAGE_1, true);
+        setAppOps(UID_10_2, PACKAGE_2, true);
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+        areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, NONE);
+        areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        // Toggle power saver, should still be the same.
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        mPowerSaveMode = false;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, NONE);
+        areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        // Clear the app ops and update the whitelist.
+        setAppOps(UID_1, PACKAGE_1, false);
+        setAppOps(UID_10_2, PACKAGE_2, false);
+
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_1, PACKAGE_1, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_1}, new int[] {UID_2});
+
+        areRestricted(instance, UID_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
+        areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
+        areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        // Again, make sure toggling the global state doesn't change it.
+        mPowerSaveMode = false;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        areRestricted(instance, UID_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+        areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
+        areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
+        areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+        areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+        areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+        assertTrue(instance.isUidPowerSaveWhitelisted(UID_1));
+        assertTrue(instance.isUidPowerSaveWhitelisted(UID_10_1));
+        assertFalse(instance.isUidPowerSaveWhitelisted(UID_2));
+        assertFalse(instance.isUidPowerSaveWhitelisted(UID_10_2));
+
+        assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_1));
+        assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_10_1));
+        assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_2));
+        assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_10_2));
+    }
+
+    public void loadPersistedAppOps() throws Exception {
+        final ForceAppStandbyTrackerTestable instance = newInstance();
+
+        final List<PackageOps> ops = new ArrayList<>();
+
+        //--------------------------------------------------
+        List<OpEntry> entries = new ArrayList<>();
+        entries.add(new AppOpsManager.OpEntry(
+                AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+                AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+        entries.add(new AppOpsManager.OpEntry(
+                ForceAppStandbyTracker.TARGET_OP,
+                AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+        ops.add(new PackageOps(PACKAGE_1, UID_1, entries));
+
+        //--------------------------------------------------
+        entries = new ArrayList<>();
+        entries.add(new AppOpsManager.OpEntry(
+                ForceAppStandbyTracker.TARGET_OP,
+                AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+        ops.add(new PackageOps(PACKAGE_2, UID_2, entries));
+
+        //--------------------------------------------------
+        entries = new ArrayList<>();
+        entries.add(new AppOpsManager.OpEntry(
+                ForceAppStandbyTracker.TARGET_OP,
+                AppOpsManager.MODE_ALLOWED, 0, 0, 0, 0, null));
+
+        ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries));
+
+        //--------------------------------------------------
+        entries = new ArrayList<>();
+        entries.add(new AppOpsManager.OpEntry(
+                ForceAppStandbyTracker.TARGET_OP,
+                AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+        entries.add(new AppOpsManager.OpEntry(
+                AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+                AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+        ops.add(new PackageOps(PACKAGE_3, UID_10_3, entries));
+
+        callStart(instance);
+
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_3, PACKAGE_3));
+
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_3, PACKAGE_3));
+    }
+
+    private void assertNoCallbacks(Listener l) throws Exception {
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+    }
+
+    @Test
+    public void testPowerSaveListener() throws Exception {
+        final ForceAppStandbyTrackerTestable instance = newInstance();
+        callStart(instance);
+
+        ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+        instance.addListener(l);
+
+        // Power save on.
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Power save off.
+        mPowerSaveMode = false;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(1)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Power save on.
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        assertNoCallbacks(l);
+    }
+
+    @Test
+    public void testAllListeners() throws Exception {
+        final ForceAppStandbyTrackerTestable instance = newInstance();
+        callStart(instance);
+
+        ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+        instance.addListener(l);
+
+        // -------------------------------------------------------------------------
+        // Test with apppops.
+
+        setAppOps(UID_10_2, PACKAGE_2, true);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        setAppOps(UID_10_2, PACKAGE_2, false);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+        reset(l);
+
+        setAppOps(UID_10_2, PACKAGE_2, false);
+
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+
+        // Unrestrict while battery saver is on. Shouldn't fire.
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        // Note toggling appops while BS is on will suppress unblockAlarmsForUidPackage().
+        setAppOps(UID_10_2, PACKAGE_2, true);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Battery saver off.
+        mPowerSaveMode = false;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(1)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // -------------------------------------------------------------------------
+        // Tests with system/user/temp whitelist.
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(1)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Update temp whitelist.
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Do the same thing with battery saver on. (Currently same callbacks are called.)
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(1)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Update temp whitelist.
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3});
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(1)).updateAllJobs();
+        verify(l, times(0)).updateJobsForUid(anyInt());
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+
+        // -------------------------------------------------------------------------
+        // Tests with proc state changes.
+
+        // With battery save.
+        mPowerSaveMode = true;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        mIUidObserver.onUidActive(UID_10_1);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        mIUidObserver.onUidGone(UID_10_1, true);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        mIUidObserver.onUidActive(UID_10_1);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        mIUidObserver.onUidIdle(UID_10_1, true);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        // Without battery save.
+        mPowerSaveMode = false;
+        mPowerSaveObserver.accept(getPowerSaveState());
+
+        mIUidObserver.onUidActive(UID_10_1);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        mIUidObserver.onUidGone(UID_10_1, true);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        mIUidObserver.onUidActive(UID_10_1);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+
+        mIUidObserver.onUidIdle(UID_10_1, true);
+
+        waitUntilMainHandlerDrain();
+        verify(l, times(0)).updateAllJobs();
+        verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+        verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+        verify(l, times(0)).unblockAllUnrestrictedAlarms();
+        verify(l, times(0)).unblockAlarmsForUid(anyInt());
+        verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+        reset(l);
+    }
+
+    @Test
+    public void testUserRemoved() throws Exception {
+        final ForceAppStandbyTrackerTestable instance = newInstance();
+        callStart(instance);
+
+        mIUidObserver.onUidActive(UID_1);
+        mIUidObserver.onUidActive(UID_10_1);
+
+        setAppOps(UID_2, PACKAGE_2, true);
+        setAppOps(UID_10_2, PACKAGE_2, true);
+
+        assertTrue(instance.isInForeground(UID_1));
+        assertTrue(instance.isInForeground(UID_10_1));
+
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+        final Intent intent = new Intent(Intent.ACTION_USER_REMOVED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, 10);
+        mReceiver.onReceive(mMockContext, intent);
+
+        waitUntilMainHandlerDrain();
+
+        assertTrue(instance.isInForeground(UID_1));
+        assertFalse(instance.isInForeground(UID_10_1));
+
+        assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+        assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+    }
+
+    static int[] array(int... appIds) {
+        Arrays.sort(appIds);
+        return appIds;
+    }
+
+    private final Random mRandom = new Random();
+
+    int[] makeRandomArray() {
+        final ArrayList<Integer> list = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            if (mRandom.nextDouble() < 0.5) {
+                list.add(i);
+            }
+        }
+        return Arrays.stream(list.toArray(new Integer[list.size()]))
+                .mapToInt(Integer::intValue).toArray();
+    }
+
+    static boolean isAnyAppIdUnwhitelistedSlow(int[] prevArray, int[] newArray) {
+        Arrays.sort(newArray); // Just in case...
+        for (int p : prevArray) {
+            if (Arrays.binarySearch(newArray, p) < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void checkAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray, boolean expected) {
+        assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
+                expected, ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
+
+        // Also test isAnyAppIdUnwhitelistedSlow.
+        assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
+                expected, isAnyAppIdUnwhitelistedSlow(prevArray, newArray));
+    }
+
+    @Test
+    public void isAnyAppIdUnwhitelisted() {
+        checkAnyAppIdUnwhitelisted(array(), array(), false);
+
+        checkAnyAppIdUnwhitelisted(array(1), array(), true);
+        checkAnyAppIdUnwhitelisted(array(1), array(1), false);
+        checkAnyAppIdUnwhitelisted(array(1), array(0, 1), false);
+        checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false);
+        checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false);
+
+        checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(), true);
+        checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2), true);
+        checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2, 10), false);
+        checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(2, 10), true);
+        checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 1, 2, 4, 3, 10), false);
+        checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 0, 1, 2, 10), false);
+
+        // Random test
+        int trueCount = 0;
+        final int count = 10000;
+        for (int i = 0; i < count; i++) {
+            final int[] array1 = makeRandomArray();
+            final int[] array2 = makeRandomArray();
+
+            final boolean expected = isAnyAppIdUnwhitelistedSlow(array1, array2);
+            final boolean actual = ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(array1, array2);
+
+            assertEquals("Input: " + Arrays.toString(array1) + " " + Arrays.toString(array2),
+                    expected, actual);
+            if (expected) {
+                trueCount++;
+            }
+        }
+
+        // Make sure makeRandomArray() didn't generate all same arrays by accident.
+        assertTrue(trueCount > 0);
+        assertTrue(trueCount < count);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 4078829..fbcccf0 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -37,7 +37,6 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
 
-import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -61,7 +60,6 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -116,12 +114,10 @@
 import android.util.TrustedTime;
 
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -130,6 +126,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.MethodRule;
@@ -142,12 +139,10 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.PrintWriter;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -182,6 +177,7 @@
     "com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner"
  * </code></pre>
  */
+@Ignore
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class NetworkPolicyManagerServiceTest {
@@ -191,8 +187,6 @@
     private static final String TEST_IFACE = "test0";
     private static final String TEST_SSID = "AndroidAP";
 
-    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
-
     private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
 
     /**
@@ -1109,14 +1103,6 @@
         final long procStateSeq = 222;
         callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq);
         verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
-
-        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        final IndentingPrintWriter writer = new IndentingPrintWriter(
-                new PrintWriter(outputStream), " ");
-        mService.mObservedHistory.dumpUL(writer);
-        writer.flush();
-        assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
-                outputStream.toString().trim());
     }
 
     private void callOnUidStateChanged(int uid, int procState, long procStateSeq)
@@ -1129,59 +1115,6 @@
         latch.await(2, TimeUnit.SECONDS);
     }
 
-    @Test
-    public void testProcStateHistory() {
-        // Verify dump works correctly with no elements added.
-        verifyProcStateHistoryDump(0);
-
-        // Add items upto half of the max capacity and verify that dump works correctly.
-        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
-
-        // Add items upto the max capacity and verify that dump works correctly.
-        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
-
-        // Add more items than max capacity and verify that dump works correctly.
-        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
-
-    }
-
-    private void verifyProcStateHistoryDump(int count) {
-        final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
-        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        final IndentingPrintWriter writer = new IndentingPrintWriter(
-                new PrintWriter(outputStream), " ");
-
-        if (count == 0) {
-            // Verify with no uid info written to history.
-            history.dumpUL(writer);
-            writer.flush();
-            assertEquals("When no uid info is there, dump should contain NONE",
-                    "NONE", outputStream.toString().trim());
-            return;
-        }
-
-        int uid = 111;
-        long procStateSeq = 222;
-        // Add count items and verify dump works correctly.
-        for (int i = 0; i < count; ++i) {
-            uid++;
-            procStateSeq++;
-            history.addProcStateSeqUL(uid, procStateSeq);
-        }
-        history.dumpUL(writer);
-        writer.flush();
-        final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
-        // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
-        final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
-                ? count : MAX_PROC_STATE_SEQ_HISTORY;
-        assertEquals(expectedCount, uidsDump.length);
-        for (int i = 0; i < expectedCount; ++i) {
-            assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
-            uid--;
-            procStateSeq--;
-        }
-    }
-
     private void assertCycleDayAsExpected(PersistableBundle config, int carrierCycleDay,
             boolean expectValid) {
         config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, carrierCycleDay);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index b4919b6..1cec0d9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -277,4 +277,10 @@
             verify(options, times(1)).abort();
         }
     }
+
+// TODO(b/69270257): Add test to verify task layout is passed additional data such as activity and
+// source.
+//    @Test
+//    public void testCreateTaskLayout() {
+//    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 2fffb89..d74d994 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -191,6 +191,7 @@
         private String mPackage;
         private int mFlags = 0;
         private int mTaskId = 0;
+        private int mUserId = 0;
         private IVoiceInteractionSession mVoiceSession;
 
         private ActivityStack mStack;
@@ -224,6 +225,11 @@
             return this;
         }
 
+        TaskBuilder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
         TaskBuilder setStack(ActivityStack stack) {
             mStack = stack;
             return this;
@@ -245,10 +251,12 @@
 
             final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
                     intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
+            task.userId = mUserId;
             mSupervisor.setFocusStackUnchecked("test", mStack);
             mStack.addTask(task, true, "creating test task");
             task.setStack(mStack);
             task.setWindowContainerController(mock(TaskWindowContainerController.class));
+            task.touchActiveTime();
 
             return task;
         }
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index 1adfb67..54df744 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -90,6 +90,7 @@
     @Mock private LockPatternUtils mLockPatternUtils;
     @Mock private LockTaskNotify mLockTaskNotify;
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Mock private RecentTasks mRecentTasks;
 
     private LockTaskController mLockTaskController;
     private Context mContext;
@@ -110,9 +111,10 @@
             Looper.prepare();
         }
 
+        mSupervisor.mRecentTasks = mRecentTasks;
+
         mLockTaskController = new LockTaskController(mContext, mSupervisor,
                 new ImmediatelyExecuteHandler());
-
         mLockTaskController.setWindowManager(mWindowManager);
         mLockTaskController.mStatusBarService = mStatusBarService;
         mLockTaskController.mDevicePolicyManager = mDevicePolicyManager;
@@ -601,6 +603,8 @@
                 eq(mContext.getPackageName()));
         verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class),
                 eq(mContext.getPackageName()));
+        // THEN recents should have been notified
+        verify(mRecentTasks).onLockTaskModeStateChanged(anyInt(), eq(TEST_USER_ID));
         // THEN the DO/PO should be informed about the operation
         verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME,
                 TEST_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index afece5d..13ca10c 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -23,6 +23,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -30,6 +31,8 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 
 import static java.lang.Integer.MAX_VALUE;
 
@@ -42,6 +45,7 @@
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -64,6 +68,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Random;
 import java.util.Set;
 
 /**
@@ -78,17 +83,19 @@
     private static final int TEST_QUIET_USER_ID = 20;
     private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
     private static final UserInfo QUIET_USER_INFO = new UserInfo();
-    private static final ComponentName MY_COMPONENT = new ComponentName(
-            RecentTasksTest.class.getPackage().getName(), RecentTasksTest.class.getName());
     private static int LAST_TASK_ID = 1;
+    private static int LAST_STACK_ID = 1;
     private static int INVALID_STACK_ID = 999;
 
     private Context mContext = InstrumentationRegistry.getContext();
     private ActivityManagerService mService;
+    private ActivityDisplay mDisplay;
+    private ActivityDisplay mOtherDisplay;
     private ActivityStack mStack;
+    private ActivityStack mHomeStack;
     private TestTaskPersister mTaskPersister;
-    private RecentTasks mRecentTasks;
-    private RunningTasks mRunningTasks;
+    private TestRecentTasks mRecentTasks;
+    private TestRunningTasks mRunningTasks;
 
     private static ArrayList<TaskRecord> mTasks = new ArrayList<>();
     private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>();
@@ -133,22 +140,25 @@
 
         mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
         mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
-        mRecentTasks = mService.getRecentTasks();
+        mRecentTasks = (TestRecentTasks) mService.getRecentTasks();
         mRecentTasks.loadParametersFromResources(mContext.getResources());
+        mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack);
         mCallbacksRecorder = new CallbacksRecorder();
         mRecentTasks.registerCallback(mCallbacksRecorder);
         QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
 
-        mTasks.add(createTask(".Task1"));
-        mTasks.add(createTask(".Task2"));
-        mTasks.add(createTask(".Task3"));
-        mTasks.add(createTask(".Task4"));
-        mTasks.add(createTask(".Task5"));
+        mTasks.add(createTaskBuilder(".Task1").build());
+        mTasks.add(createTaskBuilder(".Task2").build());
+        mTasks.add(createTaskBuilder(".Task3").build());
+        mTasks.add(createTaskBuilder(".Task4").build());
+        mTasks.add(createTaskBuilder(".Task5").build());
 
-        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */));
-        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */));
+        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
+        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
     }
 
     @Test
@@ -172,9 +182,9 @@
         mCallbacksRecorder.clear();
 
         // Add a task which will trigger the trimming of another
-        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1", null /* affinity */);
+        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
         documentTask1.maxRecents = 1;
-        TaskRecord documentTask2 = createDocumentTask(".DocumentTask1", null /* affinity */);
+        TaskRecord documentTask2 = createDocumentTask(".DocumentTask1");
         mRecentTasks.add(documentTask1);
         mRecentTasks.add(documentTask2);
         assertTrue(mCallbacksRecorder.added.contains(documentTask1));
@@ -194,13 +204,15 @@
 
     @Test
     public void testUsersTasks() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+
         // Setup some tasks for the users
         mTaskPersister.userTaskIdsOverride = new SparseBooleanArray();
         mTaskPersister.userTaskIdsOverride.put(1, true);
         mTaskPersister.userTaskIdsOverride.put(2, true);
         mTaskPersister.userTasksOverride = new ArrayList<>();
-        mTaskPersister.userTasksOverride.add(createTask(".UserTask1"));
-        mTaskPersister.userTasksOverride.add(createTask(".UserTask2"));
+        mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build());
+        mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build());
 
         // Assert no user tasks are initially loaded
         assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0);
@@ -235,6 +247,20 @@
 
     @Test
     public void testOrderedIteration() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+        TaskRecord task1 = createTaskBuilder(".Task1").build();
+        task1.lastActiveTime = new Random().nextInt();
+        TaskRecord task2 = createTaskBuilder(".Task1").build();
+        task2.lastActiveTime = new Random().nextInt();
+        TaskRecord task3 = createTaskBuilder(".Task1").build();
+        task3.lastActiveTime = new Random().nextInt();
+        TaskRecord task4 = createTaskBuilder(".Task1").build();
+        task4.lastActiveTime = new Random().nextInt();
+        mRecentTasks.add(task1);
+        mRecentTasks.add(task2);
+        mRecentTasks.add(task3);
+        mRecentTasks.add(task4);
+
         MutableLong prevLastActiveTime = new MutableLong(0);
         final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks();
         for (int i = 0; i < tasks.size(); i++) {
@@ -246,6 +272,8 @@
 
     @Test
     public void testTrimToGlobalMaxNumRecents() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
+
         // Limit the global maximum number of recent tasks to a fixed size
         mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */);
 
@@ -260,8 +288,9 @@
 
     @Test
     public void testTrimQuietProfileTasks() throws Exception {
-        TaskRecord qt1 = createTask(".QuietTask1", TEST_QUIET_USER_ID);
-        TaskRecord qt2 = createTask(".QuietTask2", TEST_QUIET_USER_ID);
+        mRecentTasks.setOnlyTestVisibleRange();
+        TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build();
+        TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build();
         mRecentTasks.add(qt1);
         mRecentTasks.add(qt2);
 
@@ -274,16 +303,17 @@
 
     @Test
     public void testSessionDuration() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
         mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */);
 
-        TaskRecord t1 = createTask(".Task1");
+        TaskRecord t1 = createTaskBuilder(".Task1").build();
         t1.touchActiveTime();
         mRecentTasks.add(t1);
 
         // Force a small sleep just beyond the session duration
         SystemClock.sleep(75);
 
-        TaskRecord t2 = createTask(".Task2");
+        TaskRecord t2 = createTaskBuilder(".Task2").build();
         t2.touchActiveTime();
         mRecentTasks.add(t2);
 
@@ -293,12 +323,15 @@
 
     @Test
     public void testVisibleTasks_excludedFromRecents() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
         mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */);
 
-        TaskRecord excludedTask1 = createTask(".ExcludedTask1", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
-                TEST_USER_0_ID);
-        TaskRecord excludedTask2 = createTask(".ExcludedTask2", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
-                TEST_USER_0_ID);
+        TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1")
+                .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                .build();
+        TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2")
+                .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                .build();
 
         mRecentTasks.add(excludedTask1);
         mRecentTasks.add(mTasks.get(0));
@@ -312,6 +345,7 @@
 
     @Test
     public void testVisibleTasks_minNum() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
         mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */);
 
         for (int i = 0; i < 4; i++) {
@@ -327,12 +361,12 @@
         mRecentTasks.add(mTasks.get(4));
 
         // Ensure that there are a minimum number of tasks regardless of session length
-        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
-        assertTrue(mCallbacksRecorder.removed.isEmpty());
+        assertNoTasksTrimmed();
     }
 
     @Test
     public void testVisibleTasks_maxNum() throws Exception {
+        mRecentTasks.setOnlyTestVisibleRange();
         mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
 
         for (int i = 0; i < 5; i++) {
@@ -346,16 +380,78 @@
     }
 
     @Test
+    public void testBackStackTasks_expectNoTrim() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+        final MyTestActivityStackSupervisor supervisor =
+                (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+        final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+        supervisor.setHomeStack(homeStack);
+
+        // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
+        // the tasks belong in stacks above the home stack
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build());
+
+        assertNoTasksTrimmed();
+    }
+
+    @Test
+    public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+        final MyTestActivityStackSupervisor supervisor =
+                (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+        final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+        supervisor.setHomeStack(homeStack);
+
+        // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
+        // the home stack is trimmed once a new task is added
+        final TaskRecord behindHomeTask = createTaskBuilder(".Task1")
+                .setStack(behindHomeStack)
+                .build();
+        mRecentTasks.add(behindHomeTask);
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
+
+        assertTrimmed(behindHomeTask);
+    }
+
+    @Test
+    public void testOtherDisplayTasks_expectNoTrim() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+        final MyTestActivityStackSupervisor supervisor =
+                (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+        final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+        final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor);
+        supervisor.setHomeStack(homeStack);
+
+        // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
+        // removed
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build());
+        mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build());
+
+        assertNoTasksTrimmed();
+    }
+
+    @Test
     public void testNotRecentsComponent_denyApiAccess() throws Exception {
         doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
                 anyInt(), anyInt());
 
         // Expect the following methods to fail due to recents component not being set
-        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(
-                TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
+        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
         testRecentTasksApis(false /* expectNoSecurityException */);
         // Don't throw for the following tests
-        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.DENY);
+        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY);
         testGetTasksApis(false /* expectNoSecurityException */);
     }
 
@@ -365,7 +461,7 @@
                 anyInt(), anyInt());
 
         // Set the recents component and ensure that the following calls do not fail
-        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.GRANT);
+        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
         testRecentTasksApis(true /* expectNoSecurityException */);
         testGetTasksApis(true /* expectNoSecurityException */);
     }
@@ -436,38 +532,27 @@
         mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
         mService.getTasks(MAX_VALUE);
         if (expectCallable) {
-            assertTrue(((TestRecentTasks) mRecentTasks).mLastAllowed);
-            assertTrue(((TestRunningTasks) mRunningTasks).mLastAllowed);
+            assertTrue(mRecentTasks.lastAllowed);
+            assertTrue(mRunningTasks.lastAllowed);
         } else {
-            assertFalse(((TestRecentTasks) mRecentTasks).mLastAllowed);
-            assertFalse(((TestRunningTasks) mRunningTasks).mLastAllowed);
+            assertFalse(mRecentTasks.lastAllowed);
+            assertFalse(mRunningTasks.lastAllowed);
         }
     }
 
-    private ComponentName createComponent(String className) {
-        return new ComponentName(mContext.getPackageName(), className);
+    private TaskBuilder createTaskBuilder(String className) {
+        return new TaskBuilder(mService.mStackSupervisor)
+                .setComponent(new ComponentName(mContext.getPackageName(), className))
+                .setStack(mStack)
+                .setTaskId(LAST_TASK_ID++)
+                .setUserId(TEST_USER_0_ID);
     }
 
-    private TaskRecord createTask(String className) {
-        return createTask(className, TEST_USER_0_ID);
-    }
-
-    private TaskRecord createTask(String className, int userId) {
-        return createTask(className, 0 /* flags */, userId);
-    }
-
-    private TaskRecord createTask(String className, int flags, int userId) {
-        TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
-                .setComponent(createComponent(className))
-                .setStack(mStack).setFlags(flags).setTaskId(LAST_TASK_ID++).build();
-        task.userId = userId;
-        task.touchActiveTime();
-        return task;
-    }
-
-    private TaskRecord createDocumentTask(String className, String affinity) {
-        TaskRecord task = createTask(className, FLAG_ACTIVITY_NEW_DOCUMENT, TEST_USER_0_ID);
-        task.affinity = affinity;
+    private TaskRecord createDocumentTask(String className) {
+        TaskRecord task = createTaskBuilder(className)
+                .setFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+                .build();
+        task.affinity = null;
         return task;
     }
 
@@ -476,6 +561,10 @@
         return Arrays.binarySearch(userIds, targetUserId) >= 0;
     }
 
+    private void assertNoTasksTrimmed() {
+        assertTrimmed();
+    }
+
     private void assertTrimmed(TaskRecord... tasks) {
         final ArrayList<TaskRecord> trimmed = mCallbacksRecorder.trimmed;
         final ArrayList<TaskRecord> removed = mCallbacksRecorder.removed;
@@ -532,10 +621,41 @@
         }
 
         @Override
+        public void initialize() {
+            super.initialize();
+            mDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY);
+            mOtherDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY);
+            attachDisplay(mOtherDisplay);
+            attachDisplay(mDisplay);
+        }
+
+        @Override
         RunningTasks createRunningTasks() {
             mRunningTasks = new TestRunningTasks();
             return mRunningTasks;
         }
+
+        void setHomeStack(ActivityStack stack) {
+            mHomeStack = stack;
+        }
+    }
+
+    private class MyTestActivityStack extends TestActivityStack {
+        private ActivityDisplay mDisplay = null;
+
+        MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
+            super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN,
+                    ACTIVITY_TYPE_STANDARD, true);
+            mDisplay = display;
+        }
+
+        @Override
+        ActivityDisplay getDisplay() {
+            if (mDisplay != null) {
+                return mDisplay;
+            }
+            return super.getDisplay();
+        }
     }
 
     private static class CallbacksRecorder implements Callbacks {
@@ -564,7 +684,6 @@
     }
 
     private static class TestTaskPersister extends TaskPersister {
-
         SparseBooleanArray userTaskIdsOverride;
         ArrayList<TaskRecord> userTasksOverride;
 
@@ -595,8 +714,10 @@
         static final int DENY_THROW_SECURITY_EXCEPTION = 2;
 
         private boolean mOverrideIsCallerRecents;
+        private boolean mIsTrimmableOverride;
         private int mIsCallerRecentsPolicy;
-        boolean mLastAllowed;
+
+        boolean lastAllowed;
 
         TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
                 UserController userController) {
@@ -623,24 +744,39 @@
             mIsCallerRecentsPolicy = policy;
         }
 
+        /**
+         * To simplify the setup for some tests, the caller can request that we only rely on the
+         * visible range test to determine what is trimmable. In this case, we don't try to
+         * use the stack order to determine additionally if the task is trimmable when it is not
+         * in the visible range.
+         */
+        void setOnlyTestVisibleRange() {
+            mIsTrimmableOverride = true;
+        }
+
         @Override
         ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
                 boolean getTasksAllowed,
                 boolean getDetailedTasks, int userId, int callingUid) {
-            mLastAllowed = getTasksAllowed;
+            lastAllowed = getTasksAllowed;
             return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
                     callingUid);
         }
+
+        @Override
+        protected boolean isTrimmable(TaskRecord task) {
+            return mIsTrimmableOverride || super.isTrimmable(task);
+        }
     }
 
     private static class TestRunningTasks extends RunningTasks {
-        boolean mLastAllowed;
+        boolean lastAllowed;
 
         @Override
         void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
                 int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
                 int callingUid, boolean allowed) {
-            mLastAllowed = allowed;
+            lastAllowed = allowed;
             super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
                     callingUid, allowed);
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index db317a0..a92d1db 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -20,8 +20,6 @@
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import static junit.framework.Assert.assertEquals;
-
 @SmallTest
 public class NetworkEventTest extends DpmTestBase {
 
@@ -30,6 +28,7 @@
      */
     public void testConnectEventParceling() {
         ConnectEvent event = new ConnectEvent("127.0.0.1", 80, "com.android.whateverdude", 100000);
+        event.setId(5L);
         Parcel p = Parcel.obtain();
         p.writeParcelable(event, 0);
         p.setDataPosition(0);
@@ -39,6 +38,7 @@
         assertEquals(event.getPort(), unparceledEvent.getPort());
         assertEquals(event.getPackageName(), unparceledEvent.getPackageName());
         assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp());
+        assertEquals(event.getId(), unparceledEvent.getId());
     }
 
     /**
@@ -47,6 +47,7 @@
     public void testDnsEventParceling() {
         DnsEvent event = new DnsEvent("d.android.com", new String[]{"192.168.0.1", "127.0.0.1"}, 2,
                 "com.android.whateverdude", 100000);
+        event.setId(5L);
         Parcel p = Parcel.obtain();
         p.writeParcelable(event, 0);
         p.setDataPosition(0);
@@ -59,5 +60,6 @@
                 unparceledEvent.getTotalResolvedAddressCount());
         assertEquals(event.getPackageName(), unparceledEvent.getPackageName());
         assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp());
+        assertEquals(event.getId(), unparceledEvent.getId());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 6938e0f..6a1d268 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -94,12 +94,29 @@
     }
 
     @Test
-    public void testStartStopTracker() {
+    public void testStartStopTrackerScreenOnOff() {
+        mInjector.mInteractive = false;
         startTracker(mTracker);
-        assertNotNull(mInjector.mSensorListener);
+        assertNull(mInjector.mSensorListener);
         assertNotNull(mInjector.mSettingsObserver);
         assertNotNull(mInjector.mBroadcastReceiver);
         assertTrue(mInjector.mIdleScheduled);
+        Intent onIntent = new Intent();
+        onIntent.setAction(Intent.ACTION_SCREEN_ON);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                onIntent);
+        assertNotNull(mInjector.mSensorListener);
+
+        Intent offIntent = new Intent();
+        offIntent.setAction(Intent.ACTION_SCREEN_OFF);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                offIntent);
+        assertNull(mInjector.mSensorListener);
+
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                onIntent);
+        assertNotNull(mInjector.mSensorListener);
+
         mTracker.stop();
         assertNull(mInjector.mSensorListener);
         assertNull(mInjector.mSettingsObserver);
@@ -532,7 +549,6 @@
         mInjector.waitForHandler();
     }
 
-
     private static final class Idle implements MessageQueue.IdleHandler {
         private boolean mIdle;
 
@@ -565,6 +581,7 @@
         long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
         Handler mHandler;
         boolean mIdleScheduled;
+        boolean mInteractive = true;
 
         public TestInjector(Handler handler) {
             mHandler = handler;
@@ -577,7 +594,7 @@
 
         @Override
         public void registerSensorListener(Context context,
-                SensorEventListener sensorListener) {
+                SensorEventListener sensorListener, Handler handler) {
             mSensorListener = sensorListener;
         }
 
@@ -694,5 +711,9 @@
         public void cancelIdleJob(Context context) {
             mIdleScheduled = false;
         }
+
+        public boolean isInteractive(Context context) {
+            return mInteractive;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
new file mode 100644
index 0000000..a70441d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.IApplicationToken;
+import android.view.WindowManager;
+
+public class FakeWindowState implements WindowManagerPolicy.WindowState {
+
+    public final Rect parentFrame = new Rect();
+    public final Rect displayFrame = new Rect();
+    public final Rect overscanFrame = new Rect();
+    public final Rect contentFrame = new Rect();
+    public final Rect visibleFrame = new Rect();
+    public final Rect decorFrame = new Rect();
+    public final Rect stableFrame = new Rect();
+    public Rect outsetFrame = new Rect();
+
+    public WindowManager.LayoutParams attrs;
+    public int displayId;
+    public boolean isVoiceInteraction;
+    public boolean inMultiWindowMode;
+    public boolean visible = true;
+    public int surfaceLayer = 1;
+
+    public boolean policyVisible = true;
+
+    @Override
+    public int getOwningUid() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public String getOwningPackage() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame,
+            Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
+            @Nullable Rect outsetFrame) {
+        this.parentFrame.set(parentFrame);
+        this.displayFrame.set(displayFrame);
+        this.overscanFrame.set(overlayFrame);
+        this.contentFrame.set(contentFrame);
+        this.visibleFrame.set(visibleFrame);
+        this.decorFrame.set(decorFrame);
+        this.stableFrame.set(stableFrame);
+        this.outsetFrame = outsetFrame == null ? null : new Rect(outsetFrame);
+    }
+
+    @Override
+    public Rect getFrameLw() {
+        return parentFrame;
+    }
+
+    @Override
+    public Point getShownPositionLw() {
+        return new Point(parentFrame.left, parentFrame.top);
+    }
+
+    @Override
+    public Rect getDisplayFrameLw() {
+        return displayFrame;
+    }
+
+    @Override
+    public Rect getOverscanFrameLw() {
+        return overscanFrame;
+    }
+
+    @Override
+    public Rect getContentFrameLw() {
+        return contentFrame;
+    }
+
+    @Override
+    public Rect getVisibleFrameLw() {
+        return visibleFrame;
+    }
+
+    @Override
+    public boolean getGivenInsetsPendingLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public Rect getGivenContentInsetsLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public Rect getGivenVisibleInsetsLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public WindowManager.LayoutParams getAttrs() {
+        return attrs;
+    }
+
+    @Override
+    public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public int getSystemUiVisibility() {
+        return attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility;
+    }
+
+    @Override
+    public int getSurfaceLayer() {
+        return surfaceLayer;
+    }
+
+    @Override
+    public int getBaseType() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public IApplicationToken getAppToken() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isVoiceInteraction() {
+        return isVoiceInteraction;
+    }
+
+    @Override
+    public boolean hasAppShownWindows() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isVisibleLw() {
+        return visible && policyVisible;
+    }
+
+    @Override
+    public boolean isDisplayedLw() {
+        return isVisibleLw();
+    }
+
+    @Override
+    public boolean isAnimatingLw() {
+        return false;
+    }
+
+    @Override
+    public boolean canAffectSystemUiFlags() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isGoneForLayoutLw() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isDrawnLw() {
+        return true;
+    }
+
+    @Override
+    public boolean hasDrawnLw() {
+        return true;
+    }
+
+    @Override
+    public boolean hideLw(boolean doAnimation) {
+        if (!policyVisible) {
+            return false;
+        }
+        policyVisible = false;
+        return true;
+    }
+
+    @Override
+    public boolean showLw(boolean doAnimation) {
+        if (policyVisible) {
+            return false;
+        }
+        policyVisible = true;
+        return true;
+    }
+
+    @Override
+    public boolean isAlive() {
+        return true;
+    }
+
+    @Override
+    public boolean isDefaultDisplay() {
+        return displayId == Display.DEFAULT_DISPLAY;
+    }
+
+    @Override
+    public boolean isDimming() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public int getWindowingMode() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isInMultiWindowMode() {
+        return inMultiWindowMode;
+    }
+
+    @Override
+    public int getRotationAnimationHint() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public boolean isInputMethodWindow() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public int getDisplayId() {
+        return displayId;
+    }
+
+    @Override
+    public boolean canAcquireSleepToken() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
new file mode 100644
index 0000000..0941d4f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.PixelFormat;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase {
+
+    private FakeWindowState mAppWindow;
+
+    @Before
+    public void setUp() throws Exception {
+        mAppWindow = new FakeWindowState();
+        mAppWindow.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
+                TYPE_APPLICATION,
+                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                PixelFormat.TRANSLUCENT);
+
+        addStatusBar();
+        addNavigationBar();
+    }
+
+    @Test
+    public void layoutWindowLw_appDrawsBars() throws Exception {
+        mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_appWontDrawBars() throws Exception {
+        mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+    }
+
+    @Test
+    public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception {
+        mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, NAV_BAR_HEIGHT);
+    }
+
+    @Test
+    public void addingWindow_doesNotTamperWithSysuiFlags() {
+        mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        mPolicy.addWindow(mAppWindow);
+
+        assertEquals(0, mAppWindow.attrs.systemUiVisibility);
+        assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility);
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
new file mode 100644
index 0000000..9c55707
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.testing.TestableResources;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import com.android.server.policy.keyguard.KeyguardServiceDelegate;
+import com.android.server.wm.DisplayFrames;
+
+import org.junit.Before;
+
+public class PhoneWindowManagerTestBase {
+    static final int DISPLAY_WIDTH = 500;
+    static final int DISPLAY_HEIGHT = 1000;
+
+    static final int STATUS_BAR_HEIGHT = 10;
+    static final int NAV_BAR_HEIGHT = 15;
+
+    TestablePhoneWindowManager mPolicy;
+    TestContextWrapper mContext;
+    DisplayFrames mFrames;
+
+    FakeWindowState mStatusBar;
+    FakeWindowState mNavigationBar;
+
+    @Before
+    public void setUpBase() throws Exception {
+        mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext());
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.status_bar_height, STATUS_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
+        mContext.getResourceMocker().addOverride(
+                com.android.internal.R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
+
+        mPolicy = TestablePhoneWindowManager.create(mContext);
+
+        DisplayInfo info = new DisplayInfo();
+        info.logicalWidth = DISPLAY_WIDTH;
+        info.logicalHeight = DISPLAY_HEIGHT;
+        info.rotation = ROTATION_0;
+        mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
+    }
+
+    public void addStatusBar() {
+        mStatusBar = new FakeWindowState();
+        mStatusBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT,
+                TYPE_STATUS_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
+        mStatusBar.attrs.gravity = Gravity.TOP;
+
+        mPolicy.addWindow(mStatusBar);
+        mPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT;
+    }
+
+    public void addNavigationBar() {
+        mNavigationBar = new FakeWindowState();
+        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT,
+                TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
+        mNavigationBar.attrs.gravity = Gravity.BOTTOM;
+
+        mPolicy.addWindow(mNavigationBar);
+        mPolicy.mHasNavigationBar = true;
+        mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
+    }
+
+    /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
+    public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop,
+            int expectedInsetRight, int expectedInsetBottom) {
+        assertEquals(new Rect(expectedInsetLeft, expectedInsetTop,
+                DISPLAY_WIDTH - expectedInsetRight, DISPLAY_HEIGHT - expectedInsetBottom), actual);
+    }
+
+    /**
+     * Asserts that {@code actual} is inset by the given amounts from the full display rect.
+     *
+     * Convenience wrapper for when only the top and bottom inset are non-zero.
+     */
+    public void assertInsetByTopBottom(Rect actual, int expectedInsetTop, int expectedInsetBottom) {
+        assertInsetBy(actual, 0, expectedInsetTop, 0, expectedInsetBottom);
+    }
+
+    static class TestContextWrapper extends ContextWrapper {
+        private final TestableResources mResourceMocker;
+
+        public TestContextWrapper(Context targetContext) {
+            super(targetContext);
+            mResourceMocker = new TestableResources(targetContext.getResources());
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResourceMocker.getResources();
+        }
+
+        public TestableResources getResourceMocker() {
+            return mResourceMocker;
+        }
+    }
+
+    static class TestablePhoneWindowManager extends PhoneWindowManager {
+
+        public TestablePhoneWindowManager() {
+        }
+
+        @Override
+        void initializeHdmiState() {
+            // Do nothing.
+        }
+
+        @Override
+        Context getSystemUiContext() {
+            return mContext;
+        }
+
+        void addWindow(WindowState state) {
+            if (state instanceof FakeWindowState) {
+                ((FakeWindowState) state).surfaceLayer =
+                        getWindowLayerFromTypeLw(state.getAttrs().type);
+            }
+            adjustWindowParamsLw(state, state.getAttrs(), true /* hasStatusBarPermission */);
+            assertEquals(WindowManagerGlobal.ADD_OKAY, prepareAddWindowLw(state, state.getAttrs()));
+        }
+
+        public static TestablePhoneWindowManager create(Context context) {
+            TestablePhoneWindowManager[] policy = new TestablePhoneWindowManager[1];
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+                policy[0] = new TestablePhoneWindowManager();
+                policy[0].mContext = context;
+                policy[0].mKeyguardDelegate = mock(KeyguardServiceDelegate.class);
+                policy[0].mAccessibilityManager = new AccessibilityManager(context,
+                        mock(IAccessibilityManager.class), UserHandle.USER_CURRENT);
+                policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class);
+                policy[0].onConfigurationChanged();
+            });
+            return policy[0];
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
index 7e2a7d2..7324fe6 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -15,8 +15,9 @@
  */
 package com.android.server.power.batterysaver;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -26,6 +27,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.hardware.camera2.impl.GetCommand;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
@@ -40,6 +42,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -72,6 +75,17 @@
         void injectWtf(String message, Throwable e) {
             mInjector.injectWtf(message, e);
         }
+
+        @Override
+        File injectDefaultValuesFilename() {
+            return new File(InstrumentationRegistry.getContext().getCacheDir() +
+                    "/test-default.xml");
+        }
+
+        @Override
+        boolean injectShouldSkipWrite() {
+            return false;
+        }
     }
 
     private interface Injector {
@@ -334,4 +348,57 @@
         reset(mInjector);
         testMultiWrites();
     }
+
+    @Test
+    public void testWriteReadDefault() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("file3", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+        verify(mInjector, times(1)).injectWriteToFile("file3", "33");
+
+        // Clear and reload the default.
+        assertEquals(3, mInstance.getDefaultValuesForTest().size());
+        mInstance.getDefaultValuesForTest().clear();
+        assertEquals(0, mInstance.getDefaultValuesForTest().size());
+
+        mInstance.systemReady(/*runtimeRestarted=*/ true);
+
+        assertEquals(3, mInstance.getDefaultValuesForTest().size());
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+        verify(mInjector, times(1)).injectWriteToFile("file3", "333");
+
+        // Make sure the default file still exists.
+        assertTrue(mInstance.injectDefaultValuesFilename().exists());
+
+        // Simulate a clean boot.
+        mInstance.getDefaultValuesForTest().clear();
+        assertEquals(0, mInstance.getDefaultValuesForTest().size());
+
+        mInstance.systemReady(/*runtimeRestarted=*/ false);
+
+        // Default is empty, and the file is gone.
+        assertEquals(0, mInstance.getDefaultValuesForTest().size());
+        assertFalse(mInstance.injectDefaultValuesFilename().exists());
+
+        // No WTF should have happened.
+        veriryWtf(0);
+   }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 503e1ac..fdcf57b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -23,6 +23,7 @@
 import android.app.ActivityManager.TaskDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Debug;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -339,11 +340,8 @@
 
         w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null);
         w.calculatePolicyCrop(policyCrop);
-        // If we were above system decor we wouldnt' get any cropping though
-        w.mLayer = sWm.mSystemDecorLayer + 1;
-        w.calculatePolicyCrop(policyCrop);
-        assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight);
-        w.mLayer = 1;
+        assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom);
+
         dcf.setEmpty();
         // Likewise with no decor frame we would get no crop
         w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null);
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
index f7c4b1f..04f5e5e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -42,6 +42,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 
 /**
  * Tests for the {@link WindowLayersController} class.
@@ -185,7 +188,6 @@
         // target.
         assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
         assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
         assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
         assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
 
@@ -324,4 +326,21 @@
         assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow);
         assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow);
     }
+
+    @Test
+    public void testAssignWindowLayers_ForSysUiPanels() throws Exception {
+        final WindowState navBarPanel =
+                createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel");
+        final WindowState statusBarPanel =
+                createWindow(null, TYPE_STATUS_BAR_PANEL, mDisplayContent, "StatusBarPanel");
+        final WindowState statusBarSubPanel =
+                createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel");
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows and below system windows if it is targeting an app
+        // window.
+        assertWindowLayerGreaterThan(mTransaction, navBarPanel, mNavBarWindow);
+        assertWindowLayerGreaterThan(mTransaction, statusBarPanel, mStatusBarWindow);
+        assertWindowLayerGreaterThan(mTransaction, statusBarSubPanel, statusBarPanel);
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index d359b70..7bea8a1 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -255,6 +255,7 @@
     }
 
     private void alsaFileAdded(String name) {
+        Slog.i(TAG, "alsaFileAdded(" + name + ")");
         int type = AlsaDevice.TYPE_UNKNOWN;
         int card = -1, device = -1;
 
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 9bc9cd0..dd2e192 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -19,13 +19,8 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
-import android.hardware.usb.UsbConfiguration;
 import android.hardware.usb.UsbConstants;
 import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbEndpoint;
-import android.hardware.usb.UsbInterface;
-import android.hardware.usb.UsbManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
@@ -37,7 +32,6 @@
 import com.android.server.usb.descriptors.report.TextReportCanvas;
 import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 
@@ -50,28 +44,23 @@
 
     private final Context mContext;
 
-    // contains all connected USB devices
-    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
-
     // USB busses to exclude from USB host support
     private final String[] mHostBlacklist;
 
-    private final Object mLock = new Object();
-
-    private UsbDevice mNewDevice;
-    private UsbConfiguration mNewConfiguration;
-    private UsbInterface mNewInterface;
-    private ArrayList<UsbConfiguration> mNewConfigurations;
-    private ArrayList<UsbInterface> mNewInterfaces;
-    private ArrayList<UsbEndpoint> mNewEndpoints;
-
     private final UsbAlsaManager mUsbAlsaManager;
     private final UsbSettingsManager mSettingsManager;
 
+    private final Object mLock = new Object();
     @GuardedBy("mLock")
+    // contains all connected USB devices
+    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
+
+    private Object mSettingsLock = new Object();
+    @GuardedBy("mSettingsLock")
     private UsbProfileGroupSettingsManager mCurrentSettings;
 
-    @GuardedBy("mLock")
+    private Object mHandlerLock = new Object();
+    @GuardedBy("mHandlerLock")
     private ComponentName mUsbDeviceConnectionHandler;
 
     public UsbHostManager(Context context, UsbAlsaManager alsaManager,
@@ -91,33 +80,33 @@
     }
 
     public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
-        synchronized (mLock) {
+        synchronized (mSettingsLock) {
             mCurrentSettings = settings;
         }
     }
 
     private UsbProfileGroupSettingsManager getCurrentUserSettings() {
-        synchronized (mLock) {
+        synchronized (mSettingsLock) {
             return mCurrentSettings;
         }
     }
 
     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
-        synchronized (mLock) {
+        synchronized (mHandlerLock) {
             mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
         }
     }
 
     private @Nullable ComponentName getUsbDeviceConnectionHandler() {
-        synchronized (mLock) {
+        synchronized (mHandlerLock) {
             return mUsbDeviceConnectionHandler;
         }
     }
 
-    private boolean isBlackListed(String deviceName) {
+    private boolean isBlackListed(String deviceAddress) {
         int count = mHostBlacklist.length;
         for (int i = 0; i < count; i++) {
-            if (deviceName.startsWith(mHostBlacklist[i])) {
+            if (deviceAddress.startsWith(mHostBlacklist[i])) {
                 return true;
             }
         }
@@ -136,166 +125,73 @@
     }
 
     /* Called from JNI in monitorUsbHostBus() to report new USB devices
-       Returns true if successful, in which case the JNI code will continue adding configurations,
-       interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
-       have been processed
+       Returns true if successful, i.e. the USB Audio device descriptors are
+       correctly parsed and the unique device is added to the audio device list.
      */
     @SuppressWarnings("unused")
-    private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
-            int deviceClass, int deviceSubclass, int deviceProtocol,
-            String manufacturerName, String productName, int version, String serialNumber) {
-
+    private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
+            byte[] descriptors) {
         if (DEBUG) {
-            Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
-            // Audio Class Codes:
-            // Audio: 0x01
-            // Audio Subclass Codes:
-            // undefined: 0x00
-            // audio control: 0x01
-            // audio streaming: 0x02
-            // midi streaming: 0x03
-
-            // some useful debugging info
-            Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
-                    + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
+            Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
         }
 
-        // OK this is non-obvious, but true. One can't tell if the device being attached is even
-        // potentially an audio device without parsing the interface descriptors, so punt on any
-        // such test until endUsbDeviceAdded() when we have that info.
-
-        if (isBlackListed(deviceName) ||
-                isBlackListed(deviceClass, deviceSubclass)) {
+        // check class/subclass first as it is more likely to be blacklisted
+        if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) {
+            if (DEBUG) {
+                Slog.d(TAG, "device is black listed");
+            }
             return false;
         }
 
         synchronized (mLock) {
-            if (mDevices.get(deviceName) != null) {
-                Slog.w(TAG, "device already on mDevices list: " + deviceName);
+            if (mDevices.get(deviceAddress) != null) {
+                Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
+                //TODO If this is the same peripheral as is being connected, replace
+                // it with the new connection.
                 return false;
             }
 
-            if (mNewDevice != null) {
-                Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
-                return false;
-            }
+            UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress);
+            if (parser.parseDescriptors(descriptors)) {
 
-            // Create version string in "%.%" format
-            String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF);
-
-            mNewDevice = new UsbDevice(deviceName, vendorID, productID,
-                    deviceClass, deviceSubclass, deviceProtocol,
-                    manufacturerName, productName, versionString, serialNumber);
-
-            mNewConfigurations = new ArrayList<>();
-            mNewInterfaces = new ArrayList<>();
-            mNewEndpoints = new ArrayList<>();
-        }
-
-        return true;
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
-       currently being added.  Returns true if successful, false in case of error.
-     */
-    @SuppressWarnings("unused")
-    private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
-        if (mNewConfiguration != null) {
-            mNewConfiguration.setInterfaces(
-                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
-            mNewInterfaces.clear();
-        }
-
-        mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
-        mNewConfigurations.add(mNewConfiguration);
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
-       currently being added.  Returns true if successful, false in case of error.
-     */
-    @SuppressWarnings("unused")
-    private void addUsbInterface(int id, String name, int altSetting,
-            int Class, int subClass, int protocol) {
-        if (mNewInterface != null) {
-            mNewInterface.setEndpoints(
-                    mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
-            mNewEndpoints.clear();
-        }
-
-        mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
-        mNewInterfaces.add(mNewInterface);
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
-       currently being added.  Returns true if successful, false in case of error.
-     */
-    @SuppressWarnings("unused")
-    private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
-        mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
-    @SuppressWarnings("unused")
-    private void endUsbDeviceAdded() {
-        if (DEBUG) {
-            Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
-        }
-        if (mNewInterface != null) {
-            mNewInterface.setEndpoints(
-                    mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
-        }
-        if (mNewConfiguration != null) {
-            mNewConfiguration.setInterfaces(
-                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
-        }
-
-
-        synchronized (mLock) {
-            if (mNewDevice != null) {
-                mNewDevice.setConfigurations(
-                        mNewConfigurations.toArray(
-                                new UsbConfiguration[mNewConfigurations.size()]));
-                mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
-                Slog.d(TAG, "Added device " + mNewDevice);
+                UsbDevice newDevice = parser.toAndroidUsbDevice();
+                mDevices.put(deviceAddress, newDevice);
 
                 // It is fine to call this only for the current user as all broadcasts are sent to
                 // all profiles of the user and the dialogs should only show once.
                 ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
                 if (usbDeviceConnectionHandler == null) {
-                    getCurrentUserSettings().deviceAttached(mNewDevice);
+                    getCurrentUserSettings().deviceAttached(newDevice);
                 } else {
-                    getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
+                    getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
                             usbDeviceConnectionHandler);
                 }
-                // deviceName is something like: "/dev/bus/usb/001/001"
-                UsbDescriptorParser parser = new UsbDescriptorParser();
-                boolean isInputHeadset = false;
-                boolean isOutputHeadset = false;
-                if (parser.parseDevice(mNewDevice.getDeviceName())) {
-                    isInputHeadset = parser.isInputHeadset();
-                    isOutputHeadset = parser.isOutputHeadset();
-                    Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
-                            + " , out: " + isOutputHeadset + "]");
-                }
-                mUsbAlsaManager.usbDeviceAdded(mNewDevice,
-                        isInputHeadset, isOutputHeadset);
+
+                // Headset?
+                boolean isInputHeadset = parser.isInputHeadset();
+                boolean isOutputHeadset = parser.isOutputHeadset();
+                Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
+                        + " , out: " + isOutputHeadset + "]");
+
+                mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
             } else {
-                Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
+                Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress);
+                return false;
             }
-            mNewDevice = null;
-            mNewConfigurations = null;
-            mNewInterfaces = null;
-            mNewEndpoints = null;
-            mNewConfiguration = null;
-            mNewInterface = null;
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
+        }
+
+        return true;
     }
 
     /* Called from JNI in monitorUsbHostBus to report USB device removal */
     @SuppressWarnings("unused")
-    private void usbDeviceRemoved(String deviceName) {
+    private void usbDeviceRemoved(String deviceAddress) {
         synchronized (mLock) {
-            UsbDevice device = mDevices.remove(deviceName);
+            UsbDevice device = mDevices.remove(deviceAddress);
             if (device != null) {
                 mUsbAlsaManager.usbDeviceRemoved(device);
                 mSettingsManager.usbDeviceRemoved(device);
@@ -323,32 +219,35 @@
     }
 
     /* Opens the specified USB device */
-    public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings,
+    public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings,
             String packageName, int uid) {
         synchronized (mLock) {
-            if (isBlackListed(deviceName)) {
+            if (isBlackListed(deviceAddress)) {
                 throw new SecurityException("USB device is on a restricted bus");
             }
-            UsbDevice device = mDevices.get(deviceName);
+            UsbDevice device = mDevices.get(deviceAddress);
             if (device == null) {
                 // if it is not in mDevices, it either does not exist or is blacklisted
                 throw new IllegalArgumentException(
-                        "device " + deviceName + " does not exist or is restricted");
+                        "device " + deviceAddress + " does not exist or is restricted");
             }
+
             settings.checkPermission(device, packageName, uid);
-            return nativeOpenDevice(deviceName);
+            return nativeOpenDevice(deviceAddress);
         }
     }
 
     public void dump(IndentingPrintWriter pw) {
-        synchronized (mLock) {
-            pw.println("USB Host State:");
-            for (String name : mDevices.keySet()) {
-                pw.println("  " + name + ": " + mDevices.get(name));
-            }
+        pw.println("USB Host State:");
+        synchronized (mHandlerLock) {
             if (mUsbDeviceConnectionHandler != null) {
                 pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
             }
+        }
+        synchronized (mLock) {
+            for (String name : mDevices.keySet()) {
+                pw.println("  " + name + ": " + mDevices.get(name));
+            }
 
             Collection<UsbDevice> devices = mDevices.values();
             if (devices.size() != 0) {
@@ -356,17 +255,12 @@
                 for (UsbDevice device : devices) {
                     StringBuilder stringBuilder = new StringBuilder();
 
-                    UsbDescriptorParser parser = new UsbDescriptorParser();
-                    if (parser.parseDevice(device.getDeviceName())) {
+                    UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName());
+                    if (parser.parseDevice()) {
                         UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
                         descriptorTree.parse(parser);
 
-                        UsbManager usbManager =
-                                (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
-                        UsbDeviceConnection connection = usbManager.openDevice(device);
-
-                        descriptorTree.report(new TextReportCanvas(connection, stringBuilder));
-                        connection.close();
+                        descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
 
                         stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
                                 + " , out: " + parser.isOutputHeadset() + "]");
@@ -382,5 +276,5 @@
     }
 
     private native void monitorUsbHostBus();
-    private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+    private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
 }
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java
index a35b463..7763150 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java
@@ -32,7 +32,7 @@
                                             // numbers associate with this endpoint
     private byte mControls;                 // Vers 2.0 thing
 
-    public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+    public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
         super(length, type, subtype, subclass, spec);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
index 2363c4d..75531d1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
@@ -32,7 +32,7 @@
     private byte mChannelNames;     // 10:1 Unused (0x00)
     private byte mTerminal;         // 11:1 Unused (0x00)
 
-    public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+    public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
index d348664..c7634ba 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
@@ -30,7 +30,7 @@
     private byte[] mControls;   // bitmasks of which controls are present for each channel
     private byte mNameID;       // string descriptor ID of mixer name
 
-    public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+    public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
index 9f2f09e..468ae57 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
@@ -28,7 +28,7 @@
     private byte mSourceID;         // 7:1 From Input Terminal. (0x01)
     private byte mTerminal;         // 8:1 Unused.
 
-    public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+    public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java
index 1523bb5..1d8498a 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java
@@ -33,7 +33,7 @@
                                     // min & max rates otherwise mSamFreqType rates.
                                     // All 3-byte values. All rates in Hz
 
-    public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java
index b1e7680..3c45790 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java
@@ -38,7 +38,7 @@
                                 // the min & max rates. otherwise mSamFreqType rates.
                                 // All 3-byte values. All rates in Hz
 
-    public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java
index 2d4f604..4fbbb21 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java
@@ -34,7 +34,7 @@
     private int mFormatTag;     // 5:2 The Audio Data Format that has to be used to communicate
                                 // with this interface.
 
-    public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) {
+    public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java
index eefae3d..fe1b502 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java
@@ -29,7 +29,7 @@
                                 // See audio20.pdf Appendix A.7, “Audio Function Category Codes.”
     private byte mControls;     // 8:1 See audio20.pdf Table 4-5.
 
-    public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+    public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
         super(length, type, subtype, subclass, spec);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
index 3e2ac39..ee1b32c 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
@@ -39,7 +39,7 @@
     private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the
                                 // Input Terminal.
 
-    public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+    public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
index 1b267a6..ab96585 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
@@ -33,7 +33,7 @@
     private byte mNameID;       // 12+p+N:1 Index of a string descriptor, describing the
                                 // Mixer Unit.
 
-    public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+    public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
index 67478aa..20a97af 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
@@ -34,7 +34,7 @@
     private int mControls;      // 9:2 - see Audio20.pdf Table 4-10
     private byte mTerminalID;   // 11:1 - Index of a string descriptor, describing the
 
-    public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+    public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java
index c031996..7310a3e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java
@@ -31,7 +31,7 @@
     private byte mBitResolution;    // 5:1 The number of effectively used bits from
                                     // the available bits in an audio subslot.
 
-    public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java
index dc44ff0..fe88743 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java
@@ -34,7 +34,7 @@
     /**
      * TBD
      */
-    public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java
index b44a216..b0ba02f 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java
@@ -31,7 +31,7 @@
     private byte mBitResolution;    // 5:1 The number of effectively used bits from
                                     // the available bits in an audio subframe.
 
-    public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
index 18d48a0..de20738 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
@@ -41,7 +41,7 @@
     private byte mChannelNames; // 15:1 Index of a string descriptor, describing the
                                 // name of the first physical channel.
 
-    public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) {
+    public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 6e1ce07..409e605c 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,7 +38,7 @@
     static final byte ATTRIBSMASK_SYNC  = 0x0C;
     static final byte ATTRIBMASK_TRANS  = 0x03;
 
-    public UsbACAudioControlEndpoint(int length, byte type, byte subclass) {
+    public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
         super(length, type, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index d351902..e63bb74 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,7 +24,7 @@
     private static final String TAG = "UsbACAudioStreamEndpoint";
 
     //TODO data fields...
-    public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) {
+    public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
         super(length, type, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
index 4a6839d..7ebccf3 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,16 +25,16 @@
 abstract class UsbACEndpoint extends UsbDescriptor {
     private static final String TAG = "UsbACEndpoint";
 
-    protected final byte mSubclass; // from the mSubclass member of the "enclosing"
-                                    // Interface Descriptor, not the stream.
-    protected byte mSubtype;        // 2:1 HEADER descriptor subtype
+    protected final int mSubclass; // from the mSubclass member of the "enclosing"
+                                   // Interface Descriptor, not the stream.
+    protected byte mSubtype;       // 2:1 HEADER descriptor subtype
 
-    UsbACEndpoint(int length, byte type, byte subclass) {
+    UsbACEndpoint(int length, byte type, int subclass) {
         super(length, type);
         mSubclass = subclass;
     }
 
-    public byte getSubclass() {
+    public int getSubclass() {
         return mSubclass;
     }
 
@@ -52,7 +52,7 @@
     public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
                                                 int length, byte type) {
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
-        byte subClass = interfaceDesc.getUsbSubclass();
+        int subClass = interfaceDesc.getUsbSubclass();
         switch (subClass) {
             case AUDIO_AUDIOCONTROL:
                 return new UsbACAudioControlEndpoint(length, type, subClass);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
index ab3903b..2c7ef79 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
@@ -46,7 +46,7 @@
                                 // logical channel
     private byte mUnitName;     // ?:1 Index of a string descriptor, describing this Feature Unit.
 
-    public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) {
+    public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java
index 01a355e..88d026e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java
@@ -31,7 +31,7 @@
                                 // of this descriptor header and all Unit and Terminal descriptors.
 
     public UsbACHeaderInterface(
-            int length, byte type, byte subtype, byte subclass, int adcRelease) {
+            int length, byte type, byte subtype, int subclass, int adcRelease) {
         super(length, type, subtype, subclass);
         mADCRelease = adcRelease;
     }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
index df6c53f..38c12a1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
@@ -78,10 +78,10 @@
     public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005;
 
     protected final byte mSubtype;  // 2:1 HEADER descriptor subtype
-    protected final byte mSubclass; // from the mSubclass member of the
+    protected final int mSubclass;  // from the mSubclass member of the
                                     // "enclosing" Interface Descriptor
 
-    public UsbACInterface(int length, byte type, byte subtype, byte subclass) {
+    public UsbACInterface(int length, byte type, byte subtype, int subclass) {
         super(length, type);
         mSubtype = subtype;
         mSubclass = subclass;
@@ -91,12 +91,12 @@
         return mSubtype;
     }
 
-    public byte getSubclass() {
+    public int getSubclass() {
         return mSubclass;
     }
 
     private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser,
-            ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+            ByteStream stream, int length, byte type, byte subtype, int subClass) {
         switch (subtype) {
             case ACI_HEADER:
             {
@@ -157,7 +157,7 @@
     }
 
     private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser,
-            ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+            ByteStream stream, int length, byte type, byte subtype, int subClass) {
         //int spec = parser.getUsbSpec();
         int acInterfaceSpec = parser.getACInterfaceSpec();
         switch (subtype) {
@@ -182,7 +182,7 @@
     }
 
     private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type,
-            byte subtype, byte subClass) {
+            byte subtype, int subClass) {
         switch (subtype) {
             case MSI_HEADER:
                 return new UsbMSMidiHeader(length, type, subtype, subClass);
@@ -212,7 +212,7 @@
             int length, byte type) {
         byte subtype = stream.getByte();
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
-        byte subClass = interfaceDesc.getUsbSubclass();
+        int subClass = interfaceDesc.getUsbSubclass();
         switch (subClass) {
             case AUDIO_AUDIOCONTROL:
                 return allocAudioControlDescriptor(
@@ -236,7 +236,7 @@
     public void report(ReportCanvas canvas) {
         super.report(canvas);
 
-        byte subClass = getSubclass();
+        int subClass = getSubclass();
         String subClassName = UsbStrings.getACInterfaceSubclassName(subClass);
 
         byte subtype = getSubtype();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
index 9e00a79..bd027ae 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
@@ -22,7 +22,7 @@
 public final class UsbACInterfaceUnparsed extends UsbACInterface {
     private static final String TAG = "UsbACInterfaceUnparsed";
 
-    public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) {
+    public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
index 9c31457..42ee889 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
@@ -28,7 +28,7 @@
     private byte mNumJacks;
     private byte[] mJackIds;
 
-    public UsbACMidiEndpoint(int length, byte type, byte subclass) {
+    public UsbACMidiEndpoint(int length, byte type, int subclass) {
         super(length, type, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
index 88faed9..606fa2b 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
@@ -24,7 +24,7 @@
                                     // are connected.
     protected byte mNumOutputs;     // The number of output channels
 
-    public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+    public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
index b16bc57..4644fe1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
@@ -32,7 +32,7 @@
                                 // Input Pin of this Selector Unit is connected.
     private byte mNameIndex;    // Index of a string descriptor, describing the Selector Unit.
 
-    public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) {
+    public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
index 2836508..36139d6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -32,7 +32,7 @@
     protected int mTerminalType;      // 4:2 USB Streaming. (0x0101)
     protected byte mAssocTerminal;    // 6:1 Unused (0x00)
 
-    public UsbACTerminal(int length, byte type, byte subtype, byte subclass) {
+    public UsbACTerminal(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
index 7a92f9e..5e515a1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
@@ -40,7 +40,7 @@
     public static final byte EXT_FORMAT_TYPE_II     = (byte) 0x82;
     public static final byte EXT_FORMAT_TYPE_III    = (byte) 0x83;
 
-    public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) {
+    public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) {
         super(length, type, subtype, mSubclass);
         mFormatType = formatType;
     }
@@ -66,8 +66,8 @@
      * stream.
      */
     public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
-                                                ByteStream stream, int length, byte type,
-            byte subtype, byte subclass) {
+            ByteStream stream, int length, byte type,
+            byte subtype, int subclass) {
 
         byte formatType = stream.getByte();
         int acInterfaceSpec = parser.getACInterfaceSpec();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 75279c6..993778f 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -15,8 +15,13 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbInterface;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 
+import java.util.ArrayList;
+
 /**
  * @hide
  * An USB Config Descriptor.
@@ -25,15 +30,18 @@
 public final class UsbConfigDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbConfigDescriptor";
 
-    private int mTotalLength;   // 2:2 Total length in bytes of data returned
+    private int mTotalLength;    // 2:2 Total length in bytes of data returned
     private byte mNumInterfaces; // 4:1 Number of Interfaces
-    private byte mConfigValue;  // 5:1 Value to use as an argument to select this configuration
-    private byte mConfigIndex;  // 6:1 Index of String Descriptor describing this configuration
-    private byte mAttribs;      // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
-                                //     D6 Self Powered
-                                //     D5 Remote Wakeup
-                                //     D4..0 Reserved, set to 0.
-    private byte mMaxPower;     // 8:1 Maximum Power Consumption in 2mA units
+    private int mConfigValue;    // 5:1 Value to use as an argument to select this configuration
+    private byte mConfigIndex;   // 6:1 Index of String Descriptor describing this configuration
+    private int mAttribs;        // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
+                                 //     D6 Self Powered
+                                 //     D5 Remote Wakeup
+                                 //     D4..0 Reserved, set to 0.
+    private int mMaxPower;       // 8:1 Maximum Power Consumption in 2mA units
+
+    private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
+            new ArrayList<UsbInterfaceDescriptor>();
 
     UsbConfigDescriptor(int length, byte type) {
         super(length, type);
@@ -48,7 +56,7 @@
         return mNumInterfaces;
     }
 
-    public byte getConfigValue() {
+    public int getConfigValue() {
         return mConfigValue;
     }
 
@@ -56,22 +64,38 @@
         return mConfigIndex;
     }
 
-    public byte getAttribs() {
+    public int getAttribs() {
         return mAttribs;
     }
 
-    public byte getMaxPower() {
+    public int getMaxPower() {
         return mMaxPower;
     }
 
+    void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) {
+        mInterfaceDescriptors.add(interfaceDesc);
+    }
+
+    UsbConfiguration toAndroid(UsbDescriptorParser parser) {
+        String name = parser.getDescriptorString(mConfigIndex);
+        UsbConfiguration config = new
+                UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
+        UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
+        for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
+            interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
+        }
+        config.setInterfaces(interfaces);
+        return config;
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
         mTotalLength = stream.unpackUsbShort();
         mNumInterfaces = stream.getByte();
-        mConfigValue = stream.getByte();
+        mConfigValue = stream.getUnsignedByte();
         mConfigIndex = stream.getByte();
-        mAttribs = stream.getByte();
-        mMaxPower = stream.getByte();
+        mAttribs = stream.getUnsignedByte();
+        mMaxPower = stream.getUnsignedByte();
 
         return mLength;
     }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
index 8c7565b..3fc5fe3 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
@@ -85,36 +85,36 @@
     public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48
 
     // Class IDs
-    public static final byte CLASSID_DEVICE  =      0x00;
-    public static final byte CLASSID_AUDIO =        0x01;
-    public static final byte CLASSID_COM =          0x02;
-    public static final byte CLASSID_HID =          0x03;
-    // public static final byte CLASSID_??? =       0x04;
-    public static final byte CLASSID_PHYSICAL =     0x05;
-    public static final byte CLASSID_IMAGE =        0x06;
-    public static final byte CLASSID_PRINTER =      0x07;
-    public static final byte CLASSID_STORAGE =      0x08;
-    public static final byte CLASSID_HUB =          0x09;
-    public static final byte CLASSID_CDC_CONTROL =  0x0A;
-    public static final byte CLASSID_SMART_CARD =   0x0B;
-    //public static final byte CLASSID_??? =        0x0C;
-    public static final byte CLASSID_SECURITY =     0x0D;
-    public static final byte CLASSID_VIDEO =        0x0E;
-    public static final byte CLASSID_HEALTHCARE =   0x0F;
-    public static final byte CLASSID_AUDIOVIDEO =   0x10;
-    public static final byte CLASSID_BILLBOARD =    0x11;
-    public static final byte CLASSID_TYPECBRIDGE =  0x12;
-    public static final byte CLASSID_DIAGNOSTIC =   (byte) 0xDC;
-    public static final byte CLASSID_WIRELESS =     (byte) 0xE0;
-    public static final byte CLASSID_MISC =         (byte) 0xEF;
-    public static final byte CLASSID_APPSPECIFIC =  (byte) 0xFE;
-    public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF;
+    public static final int CLASSID_DEVICE  =      0x00;
+    public static final int CLASSID_AUDIO =        0x01;
+    public static final int CLASSID_COM =          0x02;
+    public static final int CLASSID_HID =          0x03;
+    // public static final int CLASSID_??? =       0x04;
+    public static final int CLASSID_PHYSICAL =     0x05;
+    public static final int CLASSID_IMAGE =        0x06;
+    public static final int CLASSID_PRINTER =      0x07;
+    public static final int CLASSID_STORAGE =      0x08;
+    public static final int CLASSID_HUB =          0x09;
+    public static final int CLASSID_CDC_CONTROL =  0x0A;
+    public static final int CLASSID_SMART_CARD =   0x0B;
+    //public static final int CLASSID_??? =        0x0C;
+    public static final int CLASSID_SECURITY =     0x0D;
+    public static final int CLASSID_VIDEO =        0x0E;
+    public static final int CLASSID_HEALTHCARE =   0x0F;
+    public static final int CLASSID_AUDIOVIDEO =   0x10;
+    public static final int CLASSID_BILLBOARD =    0x11;
+    public static final int CLASSID_TYPECBRIDGE =  0x12;
+    public static final int CLASSID_DIAGNOSTIC =   0xDC;
+    public static final int CLASSID_WIRELESS =     0xE0;
+    public static final int CLASSID_MISC =         0xEF;
+    public static final int CLASSID_APPSPECIFIC =  0xFE;
+    public static final int CLASSID_VENDSPECIFIC = 0xFF;
 
     // Audio Subclass codes
-    public static final byte AUDIO_SUBCLASS_UNDEFINED   = 0x00;
-    public static final byte AUDIO_AUDIOCONTROL         = 0x01;
-    public static final byte AUDIO_AUDIOSTREAMING       = 0x02;
-    public static final byte AUDIO_MIDISTREAMING        = 0x03;
+    public static final int AUDIO_SUBCLASS_UNDEFINED   = 0x00;
+    public static final int AUDIO_AUDIOCONTROL         = 0x01;
+    public static final int AUDIO_AUDIOSTREAMING       = 0x02;
+    public static final int AUDIO_MIDISTREAMING        = 0x03;
 
     // Request IDs
     public static final int REQUEST_GET_STATUS         = 0x00;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index ad7bde5c..6c6bd01 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbDevice;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -25,11 +26,16 @@
  */
 public final class UsbDescriptorParser {
     private static final String TAG = "UsbDescriptorParser";
+    private static final boolean DEBUG = false;
+
+    private final String mDeviceAddr;
 
     // Descriptor Objects
+    private static final int DESCRIPTORS_ALLOC_SIZE = 128;
     private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>();
 
     private UsbDeviceDescriptor mDeviceDescriptor;
+    private UsbConfigDescriptor mCurConfigDescriptor;
     private UsbInterfaceDescriptor mCurInterfaceDescriptor;
 
     // The AudioClass spec implemented by the AudioClass Interfaces
@@ -37,7 +43,13 @@
     // Obtained from the first AudioClass Header descriptor.
     private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
 
-    public UsbDescriptorParser() {}
+    public UsbDescriptorParser(String deviceAddr) {
+        mDeviceAddr = deviceAddr;
+    }
+
+    public String getDeviceAddr() {
+        return mDeviceAddr;
+    }
 
     /**
      * @return the USB Spec value associated with the Device descriptor for the
@@ -60,6 +72,18 @@
     public int getACInterfaceSpec() {
         return mACInterfacesSpec;
     }
+
+    private class UsbDescriptorsStreamFormatException extends Exception {
+        String mMessage;
+        UsbDescriptorsStreamFormatException(String message) {
+            mMessage = message;
+        }
+
+        public String toString() {
+            return "Descriptor Stream Format Exception: " + mMessage;
+        }
+    }
+
     /**
      * The probability (as returned by getHeadsetProbability() at which we conclude
      * the peripheral is a headset.
@@ -67,7 +91,8 @@
     private static final float IN_HEADSET_TRIGGER = 0.75f;
     private static final float OUT_HEADSET_TRIGGER = 0.75f;
 
-    private UsbDescriptor allocDescriptor(ByteStream stream) {
+    private UsbDescriptor allocDescriptor(ByteStream stream)
+            throws UsbDescriptorsStreamFormatException {
         stream.resetReadCount();
 
         int length = stream.getUnsignedByte();
@@ -83,15 +108,38 @@
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
-                descriptor = new UsbConfigDescriptor(length, type);
+                descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
+                if (mDeviceDescriptor != null) {
+                    mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
+                } else {
+                    Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
+                    throw new UsbDescriptorsStreamFormatException(
+                            "Config Descriptor found with no associated Device Descriptor!");
+                }
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
                 descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
+                if (mCurConfigDescriptor != null) {
+                    mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
+                } else {
+                    Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
+                    throw new UsbDescriptorsStreamFormatException(
+                            "Interface Descriptor found with no associated Config Descriptor!");
+                }
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
                 descriptor = new UsbEndpointDescriptor(length, type);
+                if (mCurInterfaceDescriptor != null) {
+                    mCurInterfaceDescriptor.addEndpointDescriptor(
+                            (UsbEndpointDescriptor) descriptor);
+                } else {
+                    Log.e(TAG,
+                            "Endpoint Descriptor found with no associated Interface Descriptor!");
+                    throw new UsbDescriptorsStreamFormatException(
+                            "Endpoint Descriptor found with no associated Interface Descriptor!");
+                }
                 break;
 
             /*
@@ -144,8 +192,12 @@
     /**
      * @hide
      */
-    public void parseDescriptors(byte[] descriptors) {
-        mDescriptors.clear();
+    public boolean parseDescriptors(byte[] descriptors) {
+        if (DEBUG) {
+            Log.d(TAG, "parseDescriptors() - start");
+        }
+        // This will allow us to (probably) alloc mDescriptors just once.
+        mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
 
         ByteStream stream = new ByteStream(descriptors);
         while (stream.available() > 0) {
@@ -173,21 +225,36 @@
                 }
             }
         }
+        if (DEBUG) {
+            Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
+        }
+        return true;
     }
 
     /**
      * @hide
      */
-    public boolean parseDevice(String deviceAddr) {
-        byte[] rawDescriptors = getRawDescriptors(deviceAddr);
-        if (rawDescriptors != null) {
-            parseDescriptors(rawDescriptors);
-            return true;
-        }
-        return false;
+    public boolean parseDevice() {
+        byte[] rawDescriptors = getRawDescriptors();
+
+        return rawDescriptors != null
+            ? parseDescriptors(rawDescriptors) : false;
     }
 
-    private native byte[] getRawDescriptors(String deviceAddr);
+    private byte[] getRawDescriptors() {
+        return getRawDescriptors_native(mDeviceAddr);
+    }
+
+    private native byte[] getRawDescriptors_native(String deviceAddr);
+
+    /**
+     * @hide
+     */
+    public String getDescriptorString(int stringId) {
+        return getDescriptorString_native(mDeviceAddr, stringId);
+    }
+
+    private native String getDescriptorString_native(String deviceAddr, int stringId);
 
     public int getParsingSpec() {
         return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
@@ -200,6 +267,17 @@
     /**
      * @hide
      */
+    public UsbDevice toAndroidUsbDevice() {
+        if (mDeviceDescriptor == null) {
+            return null;
+        }
+
+        return mDeviceDescriptor.toAndroid(this);
+    }
+
+    /**
+     * @hide
+     */
     public ArrayList<UsbDescriptor> getDescriptors(byte type) {
         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
         for (UsbDescriptor descriptor : mDescriptors) {
@@ -213,7 +291,7 @@
     /**
      * @hide
      */
-    public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) {
+    public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
         for (UsbDescriptor descriptor : mDescriptors) {
             // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
@@ -235,7 +313,7 @@
     /**
      * @hide
      */
-    public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) {
+    public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
         for (UsbDescriptor descriptor : mDescriptors) {
             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
@@ -355,8 +433,6 @@
      * to count on the peripheral being a headset.
      */
     public boolean isInputHeadset() {
-        // TEMP
-        Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%");
         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
     }
 
@@ -410,8 +486,6 @@
      * to count on the peripheral being a headset.
      */
     public boolean isOutputHeadset() {
-        // TEMP
-        Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%");
         return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index d5cb89e..8e7f0fd 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -15,9 +15,14 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbDevice;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 import com.android.server.usb.descriptors.report.UsbStrings;
 
+import java.util.ArrayList;
+
 /**
  * @hide
  * A USB Device Descriptor.
@@ -31,9 +36,9 @@
     public static final int USBSPEC_2_0 = 0x0200;
 
     private int mSpec;          // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
-    private byte mDevClass;     // 4:1 class code
-    private byte mDevSubClass;  // 5:1 subclass code
-    private byte mProtocol;     // 6:1 protocol
+    private int mDevClass;      // 4:1 class code
+    private int mDevSubClass;   // 5:1 subclass code
+    private int mProtocol;      // 6:1 protocol
     private byte mPacketSize;   // 7:1 Maximum Packet Size for Zero Endpoint.
                                 // Valid Sizes are 8, 16, 32, 64
     private int mVendorID;      // 8:2 vendor ID
@@ -44,6 +49,9 @@
     private byte mSerialNum;    // 16:1 Index of Serial Number String Descriptor
     private byte mNumConfigs;   // 17:1 Number of Possible Configurations
 
+    private ArrayList<UsbConfigDescriptor> mConfigDescriptors =
+            new ArrayList<UsbConfigDescriptor>();
+
     UsbDeviceDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 1;
@@ -53,15 +61,15 @@
         return mSpec;
     }
 
-    public byte getDevClass() {
+    public int getDevClass() {
         return mDevClass;
     }
 
-    public byte getDevSubClass() {
+    public int getDevSubClass() {
         return mDevSubClass;
     }
 
-    public byte getProtocol() {
+    public int getProtocol() {
         return mProtocol;
     }
 
@@ -97,12 +105,41 @@
         return mNumConfigs;
     }
 
+    void addConfigDescriptor(UsbConfigDescriptor config) {
+        mConfigDescriptors.add(config);
+    }
+
+    /**
+     * @hide
+     */
+    public UsbDevice toAndroid(UsbDescriptorParser parser) {
+        String mfgName = parser.getDescriptorString(mMfgIndex);
+        String prodName = parser.getDescriptorString(mProductIndex);
+
+        // Create version string in "%.%" format
+        String versionString =
+                Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF);
+        String serialStr = parser.getDescriptorString(mSerialNum);
+
+        UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID,
+                mDevClass, mDevSubClass,
+                mProtocol, mfgName, prodName,
+                versionString, serialStr);
+        UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
+        for (int index = 0; index < mConfigDescriptors.size(); index++) {
+            configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
+        }
+        device.setConfigurations(configs);
+
+        return device;
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
         mSpec = stream.unpackUsbShort();
-        mDevClass = stream.getByte();
-        mDevSubClass = stream.getByte();
-        mProtocol = stream.getByte();
+        mDevClass = stream.getUnsignedByte();
+        mDevSubClass = stream.getUnsignedByte();
+        mProtocol = stream.getUnsignedByte();
         mPacketSize = stream.getByte();
         mVendorID = stream.unpackUsbShort();
         mProductID = stream.unpackUsbShort();
@@ -124,9 +161,9 @@
         int spec = getSpec();
         canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec));
 
-        byte devClass = getDevClass();
+        int devClass = getDevClass();
         String classStr = UsbStrings.getClassName(devClass);
-        byte devSubClass = getDevSubClass();
+        int devSubClass = getDevSubClass();
         String subClasStr = UsbStrings.getClassName(devSubClass);
         canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass"
                 + devSubClass + ": " + subClasStr);
@@ -134,12 +171,11 @@
                 + " Product ID: " + ReportCanvas.getHexString(getProductID())
                 + " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));
 
+        UsbDescriptorParser parser = canvas.getParser();
         byte mfgIndex = getMfgIndex();
-        String manufacturer =
-                UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex);
+        String manufacturer = parser.getDescriptorString(mfgIndex);
         byte productIndex = getProductIndex();
-        String product =
-                UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex);
+        String product = parser.getDescriptorString(productIndex);
 
         canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer
                 + " Product " + productIndex + ": " + product);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 6322fbe..1130238 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbEndpoint;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 
 /**
@@ -25,16 +27,16 @@
 public class UsbEndpointDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbEndpointDescriptor";
 
-    public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111;
-    public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000;
-    public static final byte DIRECTION_OUTPUT = 0x00;
-    public static final byte DIRECTION_INPUT = (byte) 0x80;
+    public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
+    public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
+    public static final int DIRECTION_OUTPUT = 0x0000;
+    public static final int DIRECTION_INPUT = (byte) 0x0080;
 
-    public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011;
-    public static final byte TRANSTYPE_CONTROL = 0x00;
-    public static final byte TRANSTYPE_ISO = 0x01;
-    public static final byte TRANSTYPE_BULK = 0x02;
-    public static final byte TRANSTYPE_INTERRUPT = 0x03;
+    public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
+    public static final int TRANSTYPE_CONTROL = 0x00;
+    public static final int TRANSTYPE_ISO = 0x01;
+    public static final int TRANSTYPE_BULK = 0x02;
+    public static final int TRANSTYPE_INTERRUPT = 0x03;
 
     public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100;
     public static final byte SYNCTYPE_NONE = 0b00000000;
@@ -42,18 +44,18 @@
     public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000;
     public static final byte SYNCTYPE_RESERVED = 0b00001100;
 
-    public static final byte MASK_ATTRIBS_USEAGE = 0b00110000;
-    public static final byte USEAGE_DATA = 0b00000000;
-    public static final byte USEAGE_FEEDBACK = 0b00010000;
-    public static final byte USEAGE_EXPLICIT = 0b00100000;
-    public static final byte USEAGE_RESERVED = 0b00110000;
+    public static final int MASK_ATTRIBS_USEAGE = 0b00110000;
+    public static final int USEAGE_DATA = 0b00000000;
+    public static final int USEAGE_FEEDBACK = 0b00010000;
+    public static final int USEAGE_EXPLICIT = 0b00100000;
+    public static final int USEAGE_RESERVED = 0b00110000;
 
-    private byte mEndpointAddress;  // 2:1 Endpoint Address
+    private int mEndpointAddress;   // 2:1 Endpoint Address
                                     // Bits 0..3b Endpoint Number.
                                     // Bits 4..6b Reserved. Set to Zero
                                     // Bits 7 Direction 0 = Out, 1 = In
                                     // (Ignored for Control Endpoints)
-    private byte mAttributes;   // 3:1 Various flags
+    private int mAttributes;    // 3:1 Various flags
                                 // Bits 0..1 Transfer Type:
                                 //     00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt
                                 // Bits 2..7 are reserved. If Isochronous endpoint,
@@ -69,7 +71,7 @@
                                 //  11: Reserved
     private int mPacketSize;    // 4:2 Maximum Packet Size this endpoint is capable of
                                 // sending or receiving
-    private byte mInterval;     // 6:1 Interval for polling endpoint data transfers. Value in
+    private int mInterval;      // 6:1 Interval for polling endpoint data transfers. Value in
                                 // frame counts.
                                 // Ignored for Bulk & Control Endpoints. Isochronous must equal
                                 // 1 and field may range from 1 to 255 for interrupt endpoints.
@@ -81,11 +83,11 @@
         mHierarchyLevel = 4;
     }
 
-    public byte getEndpointAddress() {
+    public int getEndpointAddress() {
         return mEndpointAddress;
     }
 
-    public byte getAttributes() {
+    public int getAttributes() {
         return mAttributes;
     }
 
@@ -93,7 +95,7 @@
         return mPacketSize;
     }
 
-    public byte getInterval() {
+    public int getInterval() {
         return mInterval;
     }
 
@@ -105,12 +107,16 @@
         return mSyncAddress;
     }
 
+    /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
+        return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
-        mEndpointAddress = stream.getByte();
-        mAttributes = stream.getByte();
+        mEndpointAddress = stream.getUnsignedByte();
+        mAttributes = stream.getUnsignedByte();
         mPacketSize = stream.unpackUsbShort();
-        mInterval = stream.getByte();
+        mInterval = stream.getUnsignedByte();
         if (mLength == 9) {
             mRefresh = stream.getByte();
             mSyncAddress = stream.getByte();
@@ -124,13 +130,13 @@
 
         canvas.openList();
 
-        byte address = getEndpointAddress();
+        int address = getEndpointAddress();
         canvas.writeListItem("Address: "
                 + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
                 + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
                 == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
 
-        byte attributes = getAttributes();
+        int attributes = getAttributes();
         canvas.openListItem();
         canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " ");
         switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 4eef6ca..d87b1af 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -15,9 +15,14 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 import com.android.server.usb.descriptors.report.UsbStrings;
 
+import java.util.ArrayList;
+
 /**
  * @hide
  * A common super-class for all USB Interface Descritor subtypes.
@@ -26,14 +31,17 @@
 public class UsbInterfaceDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbInterfaceDescriptor";
 
-    protected byte mInterfaceNumber;  // 2:1 Number of Interface
+    protected int mInterfaceNumber;   // 2:1 Number of Interface
     protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
     protected byte mNumEndpoints;     // 4:1 Number of Endpoints used for this interface
-    protected byte mUsbClass;         // 5:1 Class Code
-    protected byte mUsbSubclass;      // 6:1 Subclass Code
-    protected byte mProtocol;         // 7:1 Protocol Code
+    protected int mUsbClass;          // 5:1 Class Code
+    protected int mUsbSubclass;       // 6:1 Subclass Code
+    protected int mProtocol;          // 7:1 Protocol Code
     protected byte mDescrIndex;       // 8:1 Index of String Descriptor Describing this interface
 
+    private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
+            new ArrayList<UsbEndpointDescriptor>();
+
     UsbInterfaceDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 3;
@@ -41,18 +49,18 @@
 
     @Override
     public int parseRawDescriptors(ByteStream stream) {
-        mInterfaceNumber = stream.getByte();
+        mInterfaceNumber = stream.getUnsignedByte();
         mAlternateSetting = stream.getByte();
         mNumEndpoints = stream.getByte();
-        mUsbClass = stream.getByte();
-        mUsbSubclass = stream.getByte();
-        mProtocol = stream.getByte();
+        mUsbClass = stream.getUnsignedByte();
+        mUsbSubclass = stream.getUnsignedByte();
+        mProtocol = stream.getUnsignedByte();
         mDescrIndex = stream.getByte();
 
         return mLength;
     }
 
-    public byte getInterfaceNumber() {
+    public int getInterfaceNumber() {
         return mInterfaceNumber;
     }
 
@@ -64,15 +72,15 @@
         return mNumEndpoints;
     }
 
-    public byte getUsbClass() {
+    public int getUsbClass() {
         return mUsbClass;
     }
 
-    public byte getUsbSubclass() {
+    public int getUsbSubclass() {
         return mUsbSubclass;
     }
 
-    public byte getProtocol() {
+    public int getProtocol() {
         return mProtocol;
     }
 
@@ -80,13 +88,29 @@
         return mDescrIndex;
     }
 
+    void addEndpointDescriptor(UsbEndpointDescriptor endpoint) {
+        mEndpointDescriptors.add(endpoint);
+    }
+
+    UsbInterface toAndroid(UsbDescriptorParser parser) {
+        String name = parser.getDescriptorString(mDescrIndex);
+        UsbInterface ntrface = new UsbInterface(
+                mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol);
+        UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()];
+        for (int index = 0; index < mEndpointDescriptors.size(); index++) {
+            endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser);
+        }
+        ntrface.setEndpoints(endpoints);
+        return ntrface;
+    }
+
     @Override
     public void report(ReportCanvas canvas) {
         super.report(canvas);
 
-        byte usbClass = getUsbClass();
-        byte usbSubclass = getUsbSubclass();
-        byte protocol = getProtocol();
+        int usbClass = getUsbClass();
+        int usbSubclass = getUsbSubclass();
+        int protocol = getProtocol();
         String className = UsbStrings.getClassName(usbClass);
         String subclassName = "";
         if (usbClass == UsbDescriptor.CLASSID_AUDIO) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index 85a3e68..d0ca6db 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,7 +25,7 @@
 public final class UsbMSMidiHeader extends UsbACInterface {
     private static final String TAG = "UsbMSMidiHeader";
 
-    public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) {
+    public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
index 1d5cbf2..7df7cfc 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
@@ -25,7 +25,7 @@
 public final class UsbMSMidiInputJack extends UsbACInterface {
     private static final String TAG = "UsbMSMidiInputJack";
 
-    UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) {
+    UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
index 9f50240a..1879ac0 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
@@ -25,7 +25,7 @@
 public final class UsbMSMidiOutputJack extends UsbACInterface {
     private static final String TAG = "UsbMSMidiOutputJack";
 
-    public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) {
+    public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
index 99ebcca..adfc514 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.usb.descriptors.report;
 
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 /**
  * @hide
@@ -32,8 +32,8 @@
      * from the USB device.
      * @param stringBuilder Generated output gets written into this object.
      */
-    public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
-        super(connection);
+    public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+        super(parser);
 
         mStringBuilder = stringBuilder;
     }
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java
index 9e0adf5..c34dc98 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.usb.descriptors.report;
 
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 /**
  * @hide
@@ -24,22 +24,19 @@
 public abstract class ReportCanvas {
     private static final String TAG = "ReportCanvas";
 
-    private final UsbDeviceConnection mConnection;
+    private final UsbDescriptorParser mParser;
 
     /**
      * Constructor.
      * @param connection    The USB connection object used to retrieve strings
      * from the USB device.
      */
-    public ReportCanvas(UsbDeviceConnection connection) {
-        mConnection = connection;
+    public ReportCanvas(UsbDescriptorParser parser) {
+        mParser = parser;
     }
 
-    /**
-     * @returns the UsbDeviceConnection member (mConnection).
-     */
-    public UsbDeviceConnection getConnection() {
-        return mConnection;
+    public UsbDescriptorParser getParser() {
+        return mParser;
     }
 
     /**
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java
index a43569d..1e19ea1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.usb.descriptors.report;
 
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 /**
  * @hide
@@ -34,8 +34,8 @@
      * from the USB device.
      * @param stringBuilder Generated output gets written into this object.
      */
-    public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
-        super(connection);
+    public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+        super(parser);
 
         mStringBuilder = stringBuilder;
     }
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
index 64ecebc..fb4576a 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
@@ -32,8 +32,8 @@
     private static HashMap<Byte, String> sDescriptorNames;
     private static HashMap<Byte, String> sACControlInterfaceNames;
     private static HashMap<Byte, String> sACStreamingInterfaceNames;
-    private static HashMap<Byte, String> sClassNames;
-    private static HashMap<Byte, String> sAudioSubclassNames;
+    private static HashMap<Integer, String> sClassNames;
+    private static HashMap<Integer, String> sAudioSubclassNames;
     private static HashMap<Integer, String> sAudioEncodingNames;
     private static HashMap<Integer, String> sTerminalNames;
     private static HashMap<Integer, String> sFormatNames;
@@ -92,7 +92,7 @@
     }
 
     private static void initClassNames() {
-        sClassNames = new HashMap<Byte, String>();
+        sClassNames = new HashMap<Integer, String>();
         sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device");
         sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio");
         sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications");
@@ -118,7 +118,7 @@
     }
 
     private static void initAudioSubclassNames() {
-        sAudioSubclassNames = new HashMap<Byte, String>();
+        sAudioSubclassNames = new HashMap<Integer, String>();
         sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded");
         sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control");
         sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming");
@@ -300,7 +300,7 @@
     /**
      * Retrieves the name for the specified USB class ID.
      */
-    public static String getClassName(byte classID) {
+    public static String getClassName(int classID) {
         String name = sClassNames.get(classID);
         int iClassID = classID & 0xFF;
         return name != null
@@ -312,7 +312,7 @@
     /**
      * Retrieves the name for the specified USB audio subclass ID.
      */
-    public static String getAudioSubclassName(byte subClassID) {
+    public static String getAudioSubclassName(int subClassID) {
         String name = sAudioSubclassNames.get(subClassID);
         int iSubclassID = subClassID & 0xFF;
         return name != null
@@ -335,7 +335,7 @@
     /**
      * Retrieves the name for the specified USB audio interface subclass ID.
      */
-    public static String getACInterfaceSubclassName(byte subClassID) {
+    public static String getACInterfaceSubclassName(int subClassID) {
         return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming";
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 44e5314..2d93da9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -427,8 +427,11 @@
                     if (hasComponent) {
                         mShortcutServiceInternal.setShortcutHostPackage(TAG,
                                 serviceComponent.getPackageName(), mCurUser);
+                        mAmInternal.setAllowAppSwitches(TAG,
+                                serviceInfo.applicationInfo.uid, mCurUser);
                     } else {
                         mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser);
+                        mAmInternal.setAllowAppSwitches(TAG, -1, mCurUser);
                     }
                 }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1db6ef7..69371a1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -816,6 +816,14 @@
     public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
 
     /**
+     * Determines whether manage IMS conference calls is supported by a carrier.  When {@code true},
+     * manage IMS conference call is supported, {@code false otherwise}.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL =
+            "support_manage_ims_conference_call_bool";
+
+    /**
      * Determines whether High Definition audio property is displayed in the dialer UI.
      * If {@code false}, remove the HD audio property from the connection so that HD audio related
      * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1814,6 +1822,7 @@
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
         sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
index 5f2e561..d27a758 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
@@ -18,6 +18,7 @@
 
 import android.util.SparseBooleanArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.SmsAddress;
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.util.HexDump;
@@ -113,8 +114,8 @@
      * share code and logic with GSM.  Also, gather all DTMF/BCD
      * processing code in one place.
      */
-
-    private static byte[] parseToDtmf(String address) {
+    @VisibleForTesting
+    public static byte[] parseToDtmf(String address) {
         int digits = address.length();
         byte[] result = new byte[digits];
         for (int i = 0; i < digits; i++) {
@@ -196,33 +197,46 @@
     public static CdmaSmsAddress parse(String address) {
         CdmaSmsAddress addr = new CdmaSmsAddress();
         addr.address = address;
-        addr.ton = CdmaSmsAddress.TON_UNKNOWN;
-        byte[] origBytes = null;
+        addr.ton = TON_UNKNOWN;
+        addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+        addr.numberPlan = NUMBERING_PLAN_UNKNOWN;
+        addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+
+        byte[] origBytes;
         String filteredAddr = filterNumericSugar(address);
-        if (filteredAddr != null) {
-            origBytes = parseToDtmf(filteredAddr);
-        }
-        if (origBytes != null) {
-            addr.digitMode = DIGIT_MODE_4BIT_DTMF;
-            addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
-            if (address.indexOf('+') != -1) {
-                addr.ton = TON_INTERNATIONAL_OR_IP;
-            }
-        } else {
-            filteredAddr = filterWhitespace(address);
-            origBytes = UserData.stringToAscii(filteredAddr);
-            if (origBytes == null) {
-                return null;
-            }
+        if (address.contains("+") || filteredAddr == null) {
+            // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+            // NUMBER_MODE should set to 1 for network address and email address.
             addr.digitMode = DIGIT_MODE_8BIT_CHAR;
             addr.numberMode = NUMBER_MODE_DATA_NETWORK;
-            if (address.indexOf('@') != -1) {
+            filteredAddr = filterWhitespace(address);
+
+            if (address.contains("@")) {
+                // This is an email address
                 addr.ton = TON_NATIONAL_OR_EMAIL;
+            } else if (address.contains("+") && filterNumericSugar(address) != null) {
+                // This is an international number
+                // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+                // digit mode is set to 1 and number mode is set to 0, type of number should set
+                // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2
+                addr.ton = TON_INTERNATIONAL_OR_IP;
+                addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY;
+                addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+                filteredAddr = filterNumericSugar(address);
             }
+
+            origBytes = UserData.stringToAscii(filteredAddr);
+        } else {
+            // The address is not an international number and it only contains digit and *#
+            origBytes = parseToDtmf(filteredAddr);
         }
+
+        if (origBytes == null) {
+            return null;
+        }
+
         addr.origBytes = origBytes;
         addr.numberOfDigits = origBytes.length;
         return addr;
     }
-
 }
diff --git a/test-base/Android.mk b/test-base/Android.mk
new file mode 100644
index 0000000..9fc70f3
--- /dev/null
+++ b/test-base/Android.mk
@@ -0,0 +1,207 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build the android.test.base library
+# ===================================
+# This contains the junit.framework and android.test classes that were in
+# Android API level 25 excluding those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := android.test.base
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Build the legacy-test library
+# =============================
+# This contains the junit.framework and android.test classes that were in
+# Android API level 25 excluding those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := legacy-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Build the repackaged-legacy-test library
+# ========================================
+# This contains repackaged versions of the classes from legacy-test.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := repackaged-legacy-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the repackaged.android.test.base library
+# ==============================================
+# This contains repackaged versions of the classes from legacy-test.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := repackaged.android.test.base
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Generate the stub source files for android.test.base.stubs
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := \
+    core-oj \
+    core-libart \
+    framework \
+
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src
+
+ANDROID_TEST_BASE_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.base.stubs_intermediates/api.txt
+ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.base.stubs_intermediates/removed.txt
+
+ANDROID_TEST_BASE_API_FILE := $(LOCAL_PATH)/api/android-test-base-current.txt
+ANDROID_TEST_BASE_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-base-removed.txt
+
+LOCAL_DROIDDOC_OPTIONS:= \
+    -stubpackages android.test:android.test.suitebuilder.annotation:com.android.internal.util:junit.framework \
+    -stubsourceonly \
+    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.base.stubs_intermediates/src \
+    -nodocs \
+    -api $(ANDROID_TEST_BASE_OUTPUT_API_FILE) \
+    -removedApi $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE) \
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-base-api-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_base_gen_stamp := $(full_target)
+
+# Add some additional dependencies
+$(ANDROID_TEST_BASE_OUTPUT_API_FILE): $(full_target)
+$(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE): $(full_target)
+
+# Build the android.test.base.stubs library
+# =========================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.base.stubs
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
+
+# Make sure to run droiddoc first to generate the stub source files.
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_base_gen_stamp)
+android_test_base_gen_stamp :=
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.base.stubs.jar)
+
+# Check that the android.test.base.stubs library has not changed
+# ==============================================================
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+    check-android-test-base-api-current, \
+    $(ANDROID_TEST_BASE_API_FILE), \
+    $(ANDROID_TEST_BASE_OUTPUT_API_FILE), \
+    $(ANDROID_TEST_BASE_REMOVED_API_FILE), \
+    $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE), \
+    -error 2 -error 3 -error 4 -error 5 -error 6 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+    -error 25 -error 26 -error 27, \
+    cat $(LOCAL_PATH)/api/apicheck_msg_android_test_base.txt, \
+    check-android-test-base-api, \
+    $(call doc-timestamp-for,android-test-base-api-stubs-gen) \
+    ))
+
+.PHONY: check-android-test-base-api
+checkapi: check-android-test-base-api
+
+.PHONY: update-android-test-base-api
+update-api: update-android-test-base-api
+
+update-android-test-base-api: $(ANDROID_TEST_BASE_OUTPUT_API_FILE) | $(ACP)
+	@echo Copying current.txt
+	$(hide) $(ACP) $(ANDROID_TEST_BASE_OUTPUT_API_FILE) $(ANDROID_TEST_BASE_API_FILE)
+	@echo Copying removed.txt
+	$(hide) $(ACP) $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_BASE_REMOVED_API_FILE)
+
+# Build the legacy-android-test library
+# =====================================
+# This contains the android.test classes that were in Android API level 25,
+# including those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src/android) \
+    $(call all-java-files-under, ../test-runner/src/android) \
+    $(call all-java-files-under, ../test-mock/src/android) \
+    $(call all-java-files-under, src/com)
+LOCAL_MODULE := legacy-android-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the legacy.test.stubs library
+# ===================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := legacy.test.stubs
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := android.test.base.stubs
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+ifeq ($(HOST_OS),linux)
+# Build the legacy-performance-test-hostdex library
+# =================================================
+# This contains the android.test.PerformanceTestCase class only
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := src/android/test/PerformanceTestCase.java
+LOCAL_MODULE := legacy-performance-test-hostdex
+
+include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
+endif  # HOST_OS == linux
diff --git a/legacy-test/api/legacy-test-current.txt b/test-base/api/android-test-base-current.txt
similarity index 100%
rename from legacy-test/api/legacy-test-current.txt
rename to test-base/api/android-test-base-current.txt
diff --git a/legacy-test/api/legacy-test-removed.txt b/test-base/api/android-test-base-removed.txt
similarity index 100%
rename from legacy-test/api/legacy-test-removed.txt
rename to test-base/api/android-test-base-removed.txt
diff --git a/test-base/api/apicheck_msg_android_test_base.txt b/test-base/api/apicheck_msg_android_test_base.txt
new file mode 100644
index 0000000..144aecc
--- /dev/null
+++ b/test-base/api/apicheck_msg_android_test_base.txt
@@ -0,0 +1,17 @@
+
+******************************
+You have tried to change the API from what has been previously approved.
+
+To make these errors go away, you have two choices:
+   1) You can add "@hide" javadoc comments to the methods, etc. listed in the
+      errors above.
+
+   2) You can update android-test-base-current.txt by executing the following command:
+         make update-android-test-base-api
+
+      To submit the revised android-test-base-current.txt to the main Android repository,
+      you will need approval.
+******************************
+
+
+
diff --git a/legacy-test/jarjar-rules.txt b/test-base/jarjar-rules.txt
similarity index 100%
rename from legacy-test/jarjar-rules.txt
rename to test-base/jarjar-rules.txt
diff --git a/legacy-test/src/android/test/AndroidTestCase.java b/test-base/src/android/test/AndroidTestCase.java
similarity index 100%
rename from legacy-test/src/android/test/AndroidTestCase.java
rename to test-base/src/android/test/AndroidTestCase.java
diff --git a/legacy-test/src/android/test/FlakyTest.java b/test-base/src/android/test/FlakyTest.java
similarity index 100%
rename from legacy-test/src/android/test/FlakyTest.java
rename to test-base/src/android/test/FlakyTest.java
diff --git a/legacy-test/src/android/test/InstrumentationTestCase.java b/test-base/src/android/test/InstrumentationTestCase.java
similarity index 100%
rename from legacy-test/src/android/test/InstrumentationTestCase.java
rename to test-base/src/android/test/InstrumentationTestCase.java
diff --git a/legacy-test/src/android/test/InstrumentationTestSuite.java b/test-base/src/android/test/InstrumentationTestSuite.java
similarity index 100%
rename from legacy-test/src/android/test/InstrumentationTestSuite.java
rename to test-base/src/android/test/InstrumentationTestSuite.java
diff --git a/legacy-test/src/android/test/PerformanceTestCase.java b/test-base/src/android/test/PerformanceTestCase.java
similarity index 100%
rename from legacy-test/src/android/test/PerformanceTestCase.java
rename to test-base/src/android/test/PerformanceTestCase.java
diff --git a/legacy-test/src/android/test/RepetitiveTest.java b/test-base/src/android/test/RepetitiveTest.java
similarity index 100%
rename from legacy-test/src/android/test/RepetitiveTest.java
rename to test-base/src/android/test/RepetitiveTest.java
diff --git a/legacy-test/src/android/test/UiThreadTest.java b/test-base/src/android/test/UiThreadTest.java
similarity index 100%
rename from legacy-test/src/android/test/UiThreadTest.java
rename to test-base/src/android/test/UiThreadTest.java
diff --git a/legacy-test/src/android/test/package.html b/test-base/src/android/test/package.html
similarity index 100%
rename from legacy-test/src/android/test/package.html
rename to test-base/src/android/test/package.html
diff --git a/legacy-test/src/android/test/suitebuilder/annotation/LargeTest.java b/test-base/src/android/test/suitebuilder/annotation/LargeTest.java
similarity index 100%
rename from legacy-test/src/android/test/suitebuilder/annotation/LargeTest.java
rename to test-base/src/android/test/suitebuilder/annotation/LargeTest.java
diff --git a/legacy-test/src/android/test/suitebuilder/annotation/MediumTest.java b/test-base/src/android/test/suitebuilder/annotation/MediumTest.java
similarity index 100%
rename from legacy-test/src/android/test/suitebuilder/annotation/MediumTest.java
rename to test-base/src/android/test/suitebuilder/annotation/MediumTest.java
diff --git a/legacy-test/src/android/test/suitebuilder/annotation/SmallTest.java b/test-base/src/android/test/suitebuilder/annotation/SmallTest.java
similarity index 100%
rename from legacy-test/src/android/test/suitebuilder/annotation/SmallTest.java
rename to test-base/src/android/test/suitebuilder/annotation/SmallTest.java
diff --git a/legacy-test/src/android/test/suitebuilder/annotation/Smoke.java b/test-base/src/android/test/suitebuilder/annotation/Smoke.java
similarity index 100%
rename from legacy-test/src/android/test/suitebuilder/annotation/Smoke.java
rename to test-base/src/android/test/suitebuilder/annotation/Smoke.java
diff --git a/legacy-test/src/android/test/suitebuilder/annotation/Suppress.java b/test-base/src/android/test/suitebuilder/annotation/Suppress.java
similarity index 100%
rename from legacy-test/src/android/test/suitebuilder/annotation/Suppress.java
rename to test-base/src/android/test/suitebuilder/annotation/Suppress.java
diff --git a/legacy-test/src/android/test/suitebuilder/annotation/package.html b/test-base/src/android/test/suitebuilder/annotation/package.html
similarity index 100%
rename from legacy-test/src/android/test/suitebuilder/annotation/package.html
rename to test-base/src/android/test/suitebuilder/annotation/package.html
diff --git a/legacy-test/src/com/android/internal/util/Predicate.java b/test-base/src/com/android/internal/util/Predicate.java
similarity index 100%
rename from legacy-test/src/com/android/internal/util/Predicate.java
rename to test-base/src/com/android/internal/util/Predicate.java
diff --git a/legacy-test/src/junit/MODULE_LICENSE_CPL b/test-base/src/junit/MODULE_LICENSE_CPL
similarity index 100%
rename from legacy-test/src/junit/MODULE_LICENSE_CPL
rename to test-base/src/junit/MODULE_LICENSE_CPL
diff --git a/legacy-test/src/junit/README.android b/test-base/src/junit/README.android
similarity index 100%
rename from legacy-test/src/junit/README.android
rename to test-base/src/junit/README.android
diff --git a/legacy-test/src/junit/cpl-v10.html b/test-base/src/junit/cpl-v10.html
similarity index 100%
rename from legacy-test/src/junit/cpl-v10.html
rename to test-base/src/junit/cpl-v10.html
diff --git a/legacy-test/src/junit/framework/Assert.java b/test-base/src/junit/framework/Assert.java
similarity index 100%
rename from legacy-test/src/junit/framework/Assert.java
rename to test-base/src/junit/framework/Assert.java
diff --git a/legacy-test/src/junit/framework/AssertionFailedError.java b/test-base/src/junit/framework/AssertionFailedError.java
similarity index 100%
rename from legacy-test/src/junit/framework/AssertionFailedError.java
rename to test-base/src/junit/framework/AssertionFailedError.java
diff --git a/legacy-test/src/junit/framework/ComparisonCompactor.java b/test-base/src/junit/framework/ComparisonCompactor.java
similarity index 100%
rename from legacy-test/src/junit/framework/ComparisonCompactor.java
rename to test-base/src/junit/framework/ComparisonCompactor.java
diff --git a/legacy-test/src/junit/framework/ComparisonFailure.java b/test-base/src/junit/framework/ComparisonFailure.java
similarity index 100%
rename from legacy-test/src/junit/framework/ComparisonFailure.java
rename to test-base/src/junit/framework/ComparisonFailure.java
diff --git a/legacy-test/src/junit/framework/Protectable.java b/test-base/src/junit/framework/Protectable.java
similarity index 100%
rename from legacy-test/src/junit/framework/Protectable.java
rename to test-base/src/junit/framework/Protectable.java
diff --git a/legacy-test/src/junit/framework/Test.java b/test-base/src/junit/framework/Test.java
similarity index 100%
rename from legacy-test/src/junit/framework/Test.java
rename to test-base/src/junit/framework/Test.java
diff --git a/legacy-test/src/junit/framework/TestCase.java b/test-base/src/junit/framework/TestCase.java
similarity index 100%
rename from legacy-test/src/junit/framework/TestCase.java
rename to test-base/src/junit/framework/TestCase.java
diff --git a/legacy-test/src/junit/framework/TestFailure.java b/test-base/src/junit/framework/TestFailure.java
similarity index 100%
rename from legacy-test/src/junit/framework/TestFailure.java
rename to test-base/src/junit/framework/TestFailure.java
diff --git a/legacy-test/src/junit/framework/TestListener.java b/test-base/src/junit/framework/TestListener.java
similarity index 100%
rename from legacy-test/src/junit/framework/TestListener.java
rename to test-base/src/junit/framework/TestListener.java
diff --git a/legacy-test/src/junit/framework/TestResult.java b/test-base/src/junit/framework/TestResult.java
similarity index 100%
rename from legacy-test/src/junit/framework/TestResult.java
rename to test-base/src/junit/framework/TestResult.java
diff --git a/legacy-test/src/junit/framework/TestSuite.java b/test-base/src/junit/framework/TestSuite.java
similarity index 100%
rename from legacy-test/src/junit/framework/TestSuite.java
rename to test-base/src/junit/framework/TestSuite.java
diff --git a/test-mock/Android.mk b/test-mock/Android.mk
index 18da8b8..e4af17c 100644
--- a/test-mock/Android.mk
+++ b/test-mock/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_JAVA_LIBRARIES := core-oj core-libart framework legacy-test
 
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../legacy-test/jarjar-rules.txt
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../test-base/jarjar-rules.txt
 
 LOCAL_MODULE:= repackaged.android.test.mock
 
@@ -130,15 +130,4 @@
 	@echo Copying removed.txt
 	$(hide) $(ACP) $(ANDROID_TEST_MOCK_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_REMOVED_API_FILE)
 
-# Build the android.test.mock.sdk library
-# =======================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := android.test.mock.sdk
-LOCAL_SDK_VERSION := current
-
-LOCAL_STATIC_JAVA_LIBRARIES := android.test.mock.stubs
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index d0f5b32..87fe831 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -26,7 +26,7 @@
     core-oj \
     core-libart \
     framework \
-    legacy-test \
+    android.test.base \
     android.test.mock \
 
 LOCAL_MODULE:= android.test.runner
@@ -43,10 +43,10 @@
     core-oj \
     core-libart \
     framework \
-    legacy-test \
+    android.test.base \
     android.test.mock \
 
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../legacy-test/jarjar-rules.txt
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../test-base/jarjar-rules.txt
 
 LOCAL_MODULE:= repackaged.android.test.runner
 
@@ -65,7 +65,7 @@
     core-oj \
     core-libart \
     framework \
-    legacy-test \
+    android.test.base \
     android.test.mock \
 
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
@@ -104,7 +104,7 @@
 LOCAL_MODULE := android.test.runner.stubs
 
 LOCAL_JAVA_LIBRARIES := \
-    legacy.test.stubs \
+    android.test.base.stubs \
     android.test.mock.stubs \
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
diff --git a/test-runner/api/android-test-runner-current.txt b/test-runner/api/android-test-runner-current.txt
index 905cfe7..1170eb5 100644
--- a/test-runner/api/android-test-runner-current.txt
+++ b/test-runner/api/android-test-runner-current.txt
@@ -271,8 +271,6 @@
   public deprecated class TestSuiteBuilder {
     ctor public TestSuiteBuilder(java.lang.Class);
     ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader);
-    method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>);
-    method public final android.test.suitebuilder.TestSuiteBuilder addRequirements(com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>...);
     method public final junit.framework.TestSuite build();
     method public android.test.suitebuilder.TestSuiteBuilder excludePackages(java.lang.String...);
     method protected java.lang.String getSuiteName();
diff --git a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
index 6158e0c..2857696 100644
--- a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
+++ b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
@@ -119,6 +119,7 @@
      *
      * @param predicates Predicates to add to the list of requirements.
      * @return The builder for method chaining.
+     * @hide
      */
     public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) {
         this.predicates.addAll(predicates);
@@ -156,7 +157,7 @@
 
     /**
      * Override the default name for the suite being built. This should generally be called if you
-     * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which
+     * call {@code addRequirements(com.android.internal.util.Predicate[])} to make it clear which
      * tests will be included. The name you specify is automatically prefixed with the package
      * containing the tests to be run. If more than one package is specified, the first is used.
      *
@@ -215,6 +216,7 @@
      *
      * @param predicates Predicates to add to the list of requirements.
      * @return The builder for method chaining.
+     * @hide
      */
     public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) {
         ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>();
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
index 7a2344317..90a373a 100644
--- a/tests/net/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.util;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import android.support.test.filters.SmallTest;
@@ -129,6 +130,55 @@
         assertArraysEqual(expected2, buffer.toArray());
     }
 
+    @Test
+    public void testGetNextSlot() {
+        int capacity = 100;
+        RingBuffer<DummyClass1> buffer = new RingBuffer<>(DummyClass1.class, capacity);
+
+        final DummyClass1[] actual = new DummyClass1[capacity];
+        final DummyClass1[] expected = new DummyClass1[capacity];
+        for (int i = 0; i < capacity; ++i) {
+            final DummyClass1 obj = buffer.getNextSlot();
+            obj.x = capacity * i;
+            actual[i] = obj;
+            expected[i] = new DummyClass1();
+            expected[i].x = capacity * i;
+        }
+        assertArraysEqual(expected, buffer.toArray());
+
+        for (int i = 0; i < capacity; ++i) {
+            if (actual[i] != buffer.getNextSlot()) {
+                fail("getNextSlot() should re-use objects if available");
+            }
+        }
+
+        RingBuffer<DummyClass2> buffer2 = new RingBuffer<>(DummyClass2.class, capacity);
+        assertNull("getNextSlot() should return null if the object can't be initiated "
+                + "(No nullary constructor)", buffer2.getNextSlot());
+
+        RingBuffer<DummyClass3> buffer3 = new RingBuffer<>(DummyClass3.class, capacity);
+        assertNull("getNextSlot() should return null if the object can't be initiated "
+                + "(Inaccessible class)", buffer3.getNextSlot());
+    }
+
+    public static final class DummyClass1 {
+        int x;
+
+        public boolean equals(Object o) {
+            if (o instanceof DummyClass1) {
+                final DummyClass1 other = (DummyClass1) o;
+                return other.x == this.x;
+            }
+            return false;
+        }
+    }
+
+    public static final class DummyClass2 {
+        public DummyClass2(int x) {}
+    }
+
+    private static final class DummyClass3 {}
+
     static <T> void assertArraysEqual(T[] expected, T[] got) {
         if (expected.length != got.length) {
             fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
index a2fa95d..c60f07d 100644
--- a/tests/testables/src/android/testing/TestableResources.java
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -39,7 +39,8 @@
     private final Resources mResources;
     private final SparseArray<Object> mOverrides = new SparseArray<>();
 
-    TestableResources(Resources realResources) {
+    /** Creates a TestableResources instance that calls through to the given real Resources. */
+    public TestableResources(Resources realResources) {
         mResources = mock(Resources.class, withSettings()
                 .spiedInstance(realResources)
                 .defaultAnswer(this::answer));
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 24187d9..02ac86c 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -707,7 +707,9 @@
           std::unique_ptr<FileReference> file_ref =
               util::make_unique<FileReference>(dst_pool->MakeRef(
                   str, StringPool::Context(StringPool::Context::kHighPriority, config)));
-          if (util::EndsWith(*file_ref->path, ".xml")) {
+          if (type == ResourceType::kRaw) {
+            file_ref->type = ResourceFile::Type::kUnknown;
+          } else if (util::EndsWith(*file_ref->path, ".xml")) {
             file_ref->type = ResourceFile::Type::kBinaryXml;
           } else if (util::EndsWith(*file_ref->path, ".png")) {
             file_ref->type = ResourceFile::Type::kPng;
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index cca1294..423d028 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -18,6 +18,10 @@
 namespace android {
 namespace stats_log_api_gen {
 
+const int PULL_ATOM_START_ID = 1000;
+
+int maxPushedAtomId = 2;
+
 using android::os::statsd::Atom;
 
 // TODO: Support WorkSources
@@ -195,12 +199,18 @@
         fprintf(out, "     */\n");
         char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
         fprintf(out, "    %s = %d%s\n", constant.c_str(), atom->code, comma);
+        if (atom->code < PULL_ATOM_START_ID && atom->code > maxPushedAtomId) {
+            maxPushedAtomId = atom->code;
+        }
         i++;
     }
     fprintf(out, "\n");
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
+    fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
+            maxPushedAtomId);
+
     // Print write methods
     fprintf(out, "//\n");
     fprintf(out, "// Write methods\n");
diff --git a/wifi/java/android/net/wifi/BatchedScanResult.java b/wifi/java/android/net/wifi/BatchedScanResult.java
index 6d9f00f..c06543e 100644
--- a/wifi/java/android/net/wifi/BatchedScanResult.java
+++ b/wifi/java/android/net/wifi/BatchedScanResult.java
@@ -17,6 +17,7 @@
 package android.net.wifi;
 
 import android.os.Parcelable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 
 import java.util.ArrayList;
@@ -29,6 +30,7 @@
  * @removed
  */
 @Deprecated
+@SystemApi
 public class BatchedScanResult implements Parcelable {
     private static final String TAG = "BatchedScanResult";
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index a9e1e9d..abbb82a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -21,6 +21,7 @@
 
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
@@ -184,5 +185,7 @@
     void restoreBackupData(in byte[] data);
 
     void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
+
+    void startSubscriptionProvisioning(in OsuProvider provider, in IProvisioningCallback callback);
 }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 183cf0d..558004c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -33,6 +33,8 @@
 import android.net.NetworkRequest;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.ProvisioningCallback;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -3610,4 +3612,45 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Start subscription provisioning flow
+     * @param provider {@link OsuProvider} to provision with
+     * @param callback {@link ProvisioningCallback} for updates regarding provisioning flow
+     * @hide
+     */
+    public void startSubscriptionProvisioning(OsuProvider provider, ProvisioningCallback callback,
+            @Nullable Handler handler) {
+        Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+        try {
+            mService.startSubscriptionProvisioning(provider,
+                    new ProvisioningCallbackProxy(looper, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class ProvisioningCallbackProxy extends IProvisioningCallback.Stub {
+        private final Handler mHandler;
+        private final ProvisioningCallback mCallback;
+
+        ProvisioningCallbackProxy(Looper looper, ProvisioningCallback callback) {
+            mHandler = new Handler(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onProvisioningStatus(int status) {
+            mHandler.post(() -> {
+                mCallback.onProvisioningStatus(status);
+            });
+        }
+
+        @Override
+        public void onProvisioningFailure(int status) {
+            mHandler.post(() -> {
+                mCallback.onProvisioningFailure(status);
+            });
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl b/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl
new file mode 100644
index 0000000..c2cb16a
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2;
+
+/**
+ * Interface for Provisioning callback.
+ *
+ * @hide
+ */
+oneway interface IProvisioningCallback
+{
+    /**
+     * Service to manager callback providing failure notification
+     */
+    void onProvisioningFailure(int status);
+
+    /**
+     * Service to manager callback providing Provisioning status
+     */
+    void onProvisioningStatus(int status);
+}
+
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
new file mode 100644
index 0000000..8b86cdd
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2;
+
+import android.os.Handler;
+
+/**
+ * Base class for provisioning callbacks. Should be extended by applications and set when calling
+ * {@link WifiManager#startSubscriptionProvisiong(OsuProvider, ProvisioningCallback, Handler)}.
+ *
+ * @hide
+ */
+public abstract class ProvisioningCallback {
+
+   /**
+     * The reason code for Provisioning Failure due to connection failure to OSU AP.
+     * @hide
+     */
+    public static final int OSU_FAILURE_AP_CONNECTION      = 1;
+
+    /**
+     * The status code for Provisioning flow to indicate connecting to OSU AP
+     * @hide
+     */
+    public static final int OSU_STATUS_AP_CONNECTING       = 1;
+
+    /**
+     * The status code for Provisioning flow to indicate connected to OSU AP
+     * @hide
+     */
+    public static final int OSU_STATUS_AP_CONNECTED        = 2;
+
+    /**
+     * Provisioning status for OSU failure
+     * @param status indicates error condition
+     */
+    public abstract void onProvisioningFailure(int status);
+
+    /**
+     * Provisioning status when OSU is in progress
+     * @param status indicates status of OSU flow
+     */
+    public abstract void onProvisioningStatus(int status);
+}
+