Merge "Create magnifier bench scene"
diff --git a/Android.mk b/Android.mk
index c4f222e..817aa80 100644
--- a/Android.mk
+++ b/Android.mk
@@ -137,6 +137,7 @@
 	../../system/bt/binder/android/bluetooth/IBluetoothPbap.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothPbapClient.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothSap.aidl \
+	../../system/bt/binder/android/bluetooth/IBluetoothSocketManager.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothStateChangeCallback.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothHeadsetClient.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothHidDevice.aidl \
diff --git a/api/current.txt b/api/current.txt
index 823cc3f..cebd6d6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4943,6 +4943,7 @@
     method public void setInTouchMode(boolean);
     method public void start();
     method public android.app.Activity startActivitySync(android.content.Intent);
+    method public android.app.Activity startActivitySync(android.content.Intent, android.os.Bundle);
     method public deprecated void startAllocCounting();
     method public void startPerformanceSnapshot();
     method public void startProfiling();
@@ -6847,7 +6848,8 @@
     method public static final long getMinFlexMillis();
     method public long getMinLatencyMillis();
     method public static final long getMinPeriodMillis();
-    method public int getNetworkType();
+    method public deprecated int getNetworkType();
+    method public android.net.NetworkRequest getRequiredNetwork();
     method public android.content.ComponentName getService();
     method public android.os.Bundle getTransientExtras();
     method public long getTriggerContentMaxDelay();
@@ -6867,7 +6869,8 @@
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
     field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
-    field public static final int NETWORK_TYPE_METERED = 4; // 0x4
+    field public static final int NETWORK_TYPE_CELLULAR = 4; // 0x4
+    field public static final deprecated int NETWORK_TYPE_METERED = 4; // 0x4
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
     field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6886,6 +6889,7 @@
     method public android.app.job.JobInfo.Builder setPeriodic(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long, long);
     method public android.app.job.JobInfo.Builder setPersisted(boolean);
+    method public android.app.job.JobInfo.Builder setRequiredNetwork(android.net.NetworkRequest);
     method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -18273,11 +18277,13 @@
     method public synchronized void applyLocalizedPattern(java.lang.String);
     method public synchronized void applyPattern(java.lang.String);
     method public synchronized boolean areSignificantDigitsUsed();
+    method public synchronized boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
+    method public synchronized android.icu.util.Currency getCurrency();
     method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
     method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
     method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -18285,8 +18291,12 @@
     method public synchronized int getGroupingSize();
     method public synchronized java.math.MathContext getMathContext();
     method public synchronized android.icu.math.MathContext getMathContextICU();
+    method public synchronized int getMaximumFractionDigits();
+    method public synchronized int getMaximumIntegerDigits();
     method public synchronized int getMaximumSignificantDigits();
     method public synchronized byte getMinimumExponentDigits();
+    method public synchronized int getMinimumFractionDigits();
+    method public synchronized int getMinimumIntegerDigits();
     method public synchronized int getMinimumSignificantDigits();
     method public synchronized int getMultiplier();
     method public synchronized java.lang.String getNegativePrefix();
@@ -18297,13 +18307,19 @@
     method public synchronized java.lang.String getPositivePrefix();
     method public synchronized java.lang.String getPositiveSuffix();
     method public synchronized java.math.BigDecimal getRoundingIncrement();
+    method public synchronized int getRoundingMode();
     method public synchronized int getSecondaryGroupingSize();
+    method public synchronized int hashCode();
     method public synchronized boolean isDecimalPatternMatchRequired();
     method public synchronized boolean isDecimalSeparatorAlwaysShown();
     method public synchronized boolean isExponentSignAlwaysShown();
+    method public synchronized boolean isGroupingUsed();
     method public synchronized boolean isParseBigDecimal();
+    method public synchronized boolean isParseIntegerOnly();
+    method public synchronized boolean isParseStrict();
     method public synchronized boolean isScientificNotation();
     method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
+    method public synchronized void setCurrency(android.icu.util.Currency);
     method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
     method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
     method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -18312,10 +18328,15 @@
     method public synchronized void setExponentSignAlwaysShown(boolean);
     method public synchronized void setFormatWidth(int);
     method public synchronized void setGroupingSize(int);
+    method public synchronized void setGroupingUsed(boolean);
     method public synchronized void setMathContext(java.math.MathContext);
     method public synchronized void setMathContextICU(android.icu.math.MathContext);
+    method public synchronized void setMaximumFractionDigits(int);
+    method public synchronized void setMaximumIntegerDigits(int);
     method public synchronized void setMaximumSignificantDigits(int);
     method public synchronized void setMinimumExponentDigits(byte);
+    method public synchronized void setMinimumFractionDigits(int);
+    method public synchronized void setMinimumIntegerDigits(int);
     method public synchronized void setMinimumSignificantDigits(int);
     method public synchronized void setMultiplier(int);
     method public synchronized void setNegativePrefix(java.lang.String);
@@ -18323,12 +18344,15 @@
     method public synchronized void setPadCharacter(char);
     method public synchronized void setPadPosition(int);
     method public synchronized void setParseBigDecimal(boolean);
+    method public synchronized void setParseIntegerOnly(boolean);
     method public deprecated void setParseMaxDigits(int);
+    method public synchronized void setParseStrict(boolean);
     method public synchronized void setPositivePrefix(java.lang.String);
     method public synchronized void setPositiveSuffix(java.lang.String);
     method public synchronized void setRoundingIncrement(java.math.BigDecimal);
     method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
     method public synchronized void setRoundingIncrement(double);
+    method public synchronized void setRoundingMode(int);
     method public synchronized void setScientificNotation(boolean);
     method public synchronized void setSecondaryGroupingSize(int);
     method public synchronized void setSignificantDigitsUsed(boolean);
@@ -31817,6 +31841,14 @@
     method public static void setVmPolicy(android.os.StrictMode.VmPolicy);
   }
 
+  public static abstract interface StrictMode.OnThreadViolationListener {
+    method public abstract void onThreadViolation(android.os.strictmode.Violation);
+  }
+
+  public static abstract interface StrictMode.OnVmViolationListener {
+    method public abstract void onVmViolation(android.os.strictmode.Violation);
+  }
+
   public static final class StrictMode.ThreadPolicy {
     field public static final android.os.StrictMode.ThreadPolicy LAX;
   }
@@ -31837,6 +31869,7 @@
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyDialog();
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyDropBox();
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyFlashScreen();
+    method public android.os.StrictMode.ThreadPolicy.Builder penaltyListener(android.os.StrictMode.OnThreadViolationListener, java.util.concurrent.Executor);
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyLog();
     method public android.os.StrictMode.ThreadPolicy.Builder permitAll();
     method public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls();
@@ -31868,6 +31901,7 @@
     method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
     method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
     method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox();
+    method public android.os.StrictMode.VmPolicy.Builder penaltyListener(android.os.StrictMode.OnVmViolationListener, java.util.concurrent.Executor);
     method public android.os.StrictMode.VmPolicy.Builder penaltyLog();
     method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int);
   }
@@ -31875,10 +31909,12 @@
   public final class SystemClock {
     method public static long currentThreadTimeMillis();
     method public static long elapsedRealtime();
+    method public static java.time.Clock elapsedRealtimeClock();
     method public static long elapsedRealtimeNanos();
     method public static boolean setCurrentTimeMillis(long);
     method public static void sleep(long);
     method public static long uptimeMillis();
+    method public static java.time.Clock uptimeMillisClock();
   }
 
   public class TestLooperManager {
@@ -32230,6 +32266,62 @@
 
 }
 
+package android.os.strictmode {
+
+  public final class CleartextNetworkViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class CustomViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class DiskReadViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class DiskWriteViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class FileUriExposedViolation extends android.os.strictmode.Violation {
+  }
+
+  public class InstanceCountViolation extends android.os.strictmode.Violation {
+    method public long getNumberOfInstances();
+  }
+
+  public final class IntentReceiverLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class LeakedClosableViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class NetworkViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ResourceMismatchViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ServiceConnectionLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class SqliteObjectLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class UnbufferedIoViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.Throwable {
+  }
+
+  public final class WebViewMethodCalledOnWrongThreadViolation extends android.os.strictmode.Violation {
+  }
+
+}
+
 package android.preference {
 
   public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -39234,7 +39326,9 @@
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
     method public int describeContents();
+    method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
     method public int getRoute();
+    method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
     method public int getSupportedRouteMask();
     method public boolean isMuted();
     method public void writeToParcel(android.os.Parcel, int);
@@ -39366,6 +39460,7 @@
     method public final void putExtras(android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void requestBluetoothAudio(java.lang.String);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -39556,6 +39651,7 @@
     method public void onCanAddCallChanged(boolean);
     method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onSilenceRinger();
+    method public final void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
diff --git a/api/system-current.txt b/api/system-current.txt
index 76a12c5..e1f2272 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5129,6 +5129,7 @@
     method public void setInTouchMode(boolean);
     method public void start();
     method public android.app.Activity startActivitySync(android.content.Intent);
+    method public android.app.Activity startActivitySync(android.content.Intent, android.os.Bundle);
     method public deprecated void startAllocCounting();
     method public void startPerformanceSnapshot();
     method public void startProfiling();
@@ -7290,7 +7291,8 @@
     method public static final long getMinFlexMillis();
     method public long getMinLatencyMillis();
     method public static final long getMinPeriodMillis();
-    method public int getNetworkType();
+    method public deprecated int getNetworkType();
+    method public android.net.NetworkRequest getRequiredNetwork();
     method public android.content.ComponentName getService();
     method public android.os.Bundle getTransientExtras();
     method public long getTriggerContentMaxDelay();
@@ -7310,7 +7312,8 @@
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
     field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
-    field public static final int NETWORK_TYPE_METERED = 4; // 0x4
+    field public static final int NETWORK_TYPE_CELLULAR = 4; // 0x4
+    field public static final deprecated int NETWORK_TYPE_METERED = 4; // 0x4
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
     field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -7329,6 +7332,7 @@
     method public android.app.job.JobInfo.Builder setPeriodic(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long, long);
     method public android.app.job.JobInfo.Builder setPersisted(boolean);
+    method public android.app.job.JobInfo.Builder setRequiredNetwork(android.net.NetworkRequest);
     method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -19832,11 +19836,13 @@
     method public synchronized void applyLocalizedPattern(java.lang.String);
     method public synchronized void applyPattern(java.lang.String);
     method public synchronized boolean areSignificantDigitsUsed();
+    method public synchronized boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
+    method public synchronized android.icu.util.Currency getCurrency();
     method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
     method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
     method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -19844,8 +19850,12 @@
     method public synchronized int getGroupingSize();
     method public synchronized java.math.MathContext getMathContext();
     method public synchronized android.icu.math.MathContext getMathContextICU();
+    method public synchronized int getMaximumFractionDigits();
+    method public synchronized int getMaximumIntegerDigits();
     method public synchronized int getMaximumSignificantDigits();
     method public synchronized byte getMinimumExponentDigits();
+    method public synchronized int getMinimumFractionDigits();
+    method public synchronized int getMinimumIntegerDigits();
     method public synchronized int getMinimumSignificantDigits();
     method public synchronized int getMultiplier();
     method public synchronized java.lang.String getNegativePrefix();
@@ -19856,13 +19866,19 @@
     method public synchronized java.lang.String getPositivePrefix();
     method public synchronized java.lang.String getPositiveSuffix();
     method public synchronized java.math.BigDecimal getRoundingIncrement();
+    method public synchronized int getRoundingMode();
     method public synchronized int getSecondaryGroupingSize();
+    method public synchronized int hashCode();
     method public synchronized boolean isDecimalPatternMatchRequired();
     method public synchronized boolean isDecimalSeparatorAlwaysShown();
     method public synchronized boolean isExponentSignAlwaysShown();
+    method public synchronized boolean isGroupingUsed();
     method public synchronized boolean isParseBigDecimal();
+    method public synchronized boolean isParseIntegerOnly();
+    method public synchronized boolean isParseStrict();
     method public synchronized boolean isScientificNotation();
     method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
+    method public synchronized void setCurrency(android.icu.util.Currency);
     method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
     method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
     method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -19871,10 +19887,15 @@
     method public synchronized void setExponentSignAlwaysShown(boolean);
     method public synchronized void setFormatWidth(int);
     method public synchronized void setGroupingSize(int);
+    method public synchronized void setGroupingUsed(boolean);
     method public synchronized void setMathContext(java.math.MathContext);
     method public synchronized void setMathContextICU(android.icu.math.MathContext);
+    method public synchronized void setMaximumFractionDigits(int);
+    method public synchronized void setMaximumIntegerDigits(int);
     method public synchronized void setMaximumSignificantDigits(int);
     method public synchronized void setMinimumExponentDigits(byte);
+    method public synchronized void setMinimumFractionDigits(int);
+    method public synchronized void setMinimumIntegerDigits(int);
     method public synchronized void setMinimumSignificantDigits(int);
     method public synchronized void setMultiplier(int);
     method public synchronized void setNegativePrefix(java.lang.String);
@@ -19882,12 +19903,15 @@
     method public synchronized void setPadCharacter(char);
     method public synchronized void setPadPosition(int);
     method public synchronized void setParseBigDecimal(boolean);
+    method public synchronized void setParseIntegerOnly(boolean);
     method public deprecated void setParseMaxDigits(int);
+    method public synchronized void setParseStrict(boolean);
     method public synchronized void setPositivePrefix(java.lang.String);
     method public synchronized void setPositiveSuffix(java.lang.String);
     method public synchronized void setRoundingIncrement(java.math.BigDecimal);
     method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
     method public synchronized void setRoundingIncrement(double);
+    method public synchronized void setRoundingMode(int);
     method public synchronized void setScientificNotation(boolean);
     method public synchronized void setSecondaryGroupingSize(int);
     method public synchronized void setSignificantDigitsUsed(boolean);
@@ -34606,6 +34630,14 @@
     method public static void setVmPolicy(android.os.StrictMode.VmPolicy);
   }
 
+  public static abstract interface StrictMode.OnThreadViolationListener {
+    method public abstract void onThreadViolation(android.os.strictmode.Violation);
+  }
+
+  public static abstract interface StrictMode.OnVmViolationListener {
+    method public abstract void onVmViolation(android.os.strictmode.Violation);
+  }
+
   public static final class StrictMode.ThreadPolicy {
     field public static final android.os.StrictMode.ThreadPolicy LAX;
   }
@@ -34626,6 +34658,7 @@
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyDialog();
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyDropBox();
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyFlashScreen();
+    method public android.os.StrictMode.ThreadPolicy.Builder penaltyListener(android.os.StrictMode.OnThreadViolationListener, java.util.concurrent.Executor);
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyLog();
     method public android.os.StrictMode.ThreadPolicy.Builder permitAll();
     method public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls();
@@ -34657,6 +34690,7 @@
     method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
     method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
     method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox();
+    method public android.os.StrictMode.VmPolicy.Builder penaltyListener(android.os.StrictMode.OnVmViolationListener, java.util.concurrent.Executor);
     method public android.os.StrictMode.VmPolicy.Builder penaltyLog();
     method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int);
   }
@@ -34664,10 +34698,12 @@
   public final class SystemClock {
     method public static long currentThreadTimeMillis();
     method public static long elapsedRealtime();
+    method public static java.time.Clock elapsedRealtimeClock();
     method public static long elapsedRealtimeNanos();
     method public static boolean setCurrentTimeMillis(long);
     method public static void sleep(long);
     method public static long uptimeMillis();
+    method public static java.time.Clock uptimeMillisClock();
   }
 
   public class TestLooperManager {
@@ -35102,6 +35138,62 @@
 
 }
 
+package android.os.strictmode {
+
+  public final class CleartextNetworkViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class CustomViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class DiskReadViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class DiskWriteViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class FileUriExposedViolation extends android.os.strictmode.Violation {
+  }
+
+  public class InstanceCountViolation extends android.os.strictmode.Violation {
+    method public long getNumberOfInstances();
+  }
+
+  public final class IntentReceiverLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class LeakedClosableViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class NetworkViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ResourceMismatchViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ServiceConnectionLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class SqliteObjectLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class UnbufferedIoViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.Throwable {
+  }
+
+  public final class WebViewMethodCalledOnWrongThreadViolation extends android.os.strictmode.Violation {
+  }
+
+}
+
 package android.permissionpresenterservice {
 
   public abstract class RuntimePermissionPresenterService extends android.app.Service {
@@ -42560,7 +42652,9 @@
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
     method public int describeContents();
+    method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
     method public int getRoute();
+    method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
     method public int getSupportedRouteMask();
     method public boolean isMuted();
     method public void writeToParcel(android.os.Parcel, int);
@@ -42699,6 +42793,7 @@
     method public final void putExtras(android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void requestBluetoothAudio(java.lang.String);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
@@ -42892,6 +42987,7 @@
     method public deprecated void onPhoneCreated(android.telecom.Phone);
     method public deprecated void onPhoneDestroyed(android.telecom.Phone);
     method public void onSilenceRinger();
+    method public final void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
@@ -43030,6 +43126,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Call> getCalls();
     method public final void removeListener(android.telecom.Phone.Listener);
+    method public void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 0cb6055..92b41a8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4972,6 +4972,7 @@
     method public void setInTouchMode(boolean);
     method public void start();
     method public android.app.Activity startActivitySync(android.content.Intent);
+    method public android.app.Activity startActivitySync(android.content.Intent, android.os.Bundle);
     method public deprecated void startAllocCounting();
     method public void startPerformanceSnapshot();
     method public void startProfiling();
@@ -6921,7 +6922,8 @@
     method public static final long getMinFlexMillis();
     method public long getMinLatencyMillis();
     method public static final long getMinPeriodMillis();
-    method public int getNetworkType();
+    method public deprecated int getNetworkType();
+    method public android.net.NetworkRequest getRequiredNetwork();
     method public android.content.ComponentName getService();
     method public android.os.Bundle getTransientExtras();
     method public long getTriggerContentMaxDelay();
@@ -6941,7 +6943,8 @@
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
     field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
-    field public static final int NETWORK_TYPE_METERED = 4; // 0x4
+    field public static final int NETWORK_TYPE_CELLULAR = 4; // 0x4
+    field public static final deprecated int NETWORK_TYPE_METERED = 4; // 0x4
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
     field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6960,6 +6963,7 @@
     method public android.app.job.JobInfo.Builder setPeriodic(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long, long);
     method public android.app.job.JobInfo.Builder setPersisted(boolean);
+    method public android.app.job.JobInfo.Builder setRequiredNetwork(android.net.NetworkRequest);
     method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -18419,11 +18423,13 @@
     method public synchronized void applyLocalizedPattern(java.lang.String);
     method public synchronized void applyPattern(java.lang.String);
     method public synchronized boolean areSignificantDigitsUsed();
+    method public synchronized boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
     method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
+    method public synchronized android.icu.util.Currency getCurrency();
     method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
     method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
     method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -18431,8 +18437,12 @@
     method public synchronized int getGroupingSize();
     method public synchronized java.math.MathContext getMathContext();
     method public synchronized android.icu.math.MathContext getMathContextICU();
+    method public synchronized int getMaximumFractionDigits();
+    method public synchronized int getMaximumIntegerDigits();
     method public synchronized int getMaximumSignificantDigits();
     method public synchronized byte getMinimumExponentDigits();
+    method public synchronized int getMinimumFractionDigits();
+    method public synchronized int getMinimumIntegerDigits();
     method public synchronized int getMinimumSignificantDigits();
     method public synchronized int getMultiplier();
     method public synchronized java.lang.String getNegativePrefix();
@@ -18443,13 +18453,19 @@
     method public synchronized java.lang.String getPositivePrefix();
     method public synchronized java.lang.String getPositiveSuffix();
     method public synchronized java.math.BigDecimal getRoundingIncrement();
+    method public synchronized int getRoundingMode();
     method public synchronized int getSecondaryGroupingSize();
+    method public synchronized int hashCode();
     method public synchronized boolean isDecimalPatternMatchRequired();
     method public synchronized boolean isDecimalSeparatorAlwaysShown();
     method public synchronized boolean isExponentSignAlwaysShown();
+    method public synchronized boolean isGroupingUsed();
     method public synchronized boolean isParseBigDecimal();
+    method public synchronized boolean isParseIntegerOnly();
+    method public synchronized boolean isParseStrict();
     method public synchronized boolean isScientificNotation();
     method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
+    method public synchronized void setCurrency(android.icu.util.Currency);
     method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
     method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
     method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -18458,10 +18474,15 @@
     method public synchronized void setExponentSignAlwaysShown(boolean);
     method public synchronized void setFormatWidth(int);
     method public synchronized void setGroupingSize(int);
+    method public synchronized void setGroupingUsed(boolean);
     method public synchronized void setMathContext(java.math.MathContext);
     method public synchronized void setMathContextICU(android.icu.math.MathContext);
+    method public synchronized void setMaximumFractionDigits(int);
+    method public synchronized void setMaximumIntegerDigits(int);
     method public synchronized void setMaximumSignificantDigits(int);
     method public synchronized void setMinimumExponentDigits(byte);
+    method public synchronized void setMinimumFractionDigits(int);
+    method public synchronized void setMinimumIntegerDigits(int);
     method public synchronized void setMinimumSignificantDigits(int);
     method public synchronized void setMultiplier(int);
     method public synchronized void setNegativePrefix(java.lang.String);
@@ -18469,12 +18490,15 @@
     method public synchronized void setPadCharacter(char);
     method public synchronized void setPadPosition(int);
     method public synchronized void setParseBigDecimal(boolean);
+    method public synchronized void setParseIntegerOnly(boolean);
     method public deprecated void setParseMaxDigits(int);
+    method public synchronized void setParseStrict(boolean);
     method public synchronized void setPositivePrefix(java.lang.String);
     method public synchronized void setPositiveSuffix(java.lang.String);
     method public synchronized void setRoundingIncrement(java.math.BigDecimal);
     method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
     method public synchronized void setRoundingIncrement(double);
+    method public synchronized void setRoundingMode(int);
     method public synchronized void setScientificNotation(boolean);
     method public synchronized void setSecondaryGroupingSize(int);
     method public synchronized void setSignificantDigitsUsed(boolean);
@@ -32059,6 +32083,14 @@
     field public static final int DETECT_VM_UNTAGGED_SOCKET = -2147483648; // 0x80000000
   }
 
+  public static abstract interface StrictMode.OnThreadViolationListener {
+    method public abstract void onThreadViolation(android.os.strictmode.Violation);
+  }
+
+  public static abstract interface StrictMode.OnVmViolationListener {
+    method public abstract void onVmViolation(android.os.strictmode.Violation);
+  }
+
   public static final class StrictMode.ThreadPolicy {
     field public static final android.os.StrictMode.ThreadPolicy LAX;
   }
@@ -32079,6 +32111,7 @@
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyDialog();
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyDropBox();
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyFlashScreen();
+    method public android.os.StrictMode.ThreadPolicy.Builder penaltyListener(android.os.StrictMode.OnThreadViolationListener, java.util.concurrent.Executor);
     method public android.os.StrictMode.ThreadPolicy.Builder penaltyLog();
     method public android.os.StrictMode.ThreadPolicy.Builder permitAll();
     method public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls();
@@ -32134,6 +32167,7 @@
     method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
     method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
     method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox();
+    method public android.os.StrictMode.VmPolicy.Builder penaltyListener(android.os.StrictMode.OnVmViolationListener, java.util.concurrent.Executor);
     method public android.os.StrictMode.VmPolicy.Builder penaltyLog();
     method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int);
   }
@@ -32141,10 +32175,12 @@
   public final class SystemClock {
     method public static long currentThreadTimeMillis();
     method public static long elapsedRealtime();
+    method public static java.time.Clock elapsedRealtimeClock();
     method public static long elapsedRealtimeNanos();
     method public static boolean setCurrentTimeMillis(long);
     method public static void sleep(long);
     method public static long uptimeMillis();
+    method public static java.time.Clock uptimeMillisClock();
   }
 
   public class TestLooperManager {
@@ -32499,6 +32535,62 @@
 
 }
 
+package android.os.strictmode {
+
+  public final class CleartextNetworkViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class CustomViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class DiskReadViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class DiskWriteViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class FileUriExposedViolation extends android.os.strictmode.Violation {
+  }
+
+  public class InstanceCountViolation extends android.os.strictmode.Violation {
+    method public long getNumberOfInstances();
+  }
+
+  public final class IntentReceiverLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class LeakedClosableViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class NetworkViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ResourceMismatchViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class ServiceConnectionLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class SqliteObjectLeakedViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class UnbufferedIoViolation extends android.os.strictmode.Violation {
+  }
+
+  public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
+  }
+
+  public abstract class Violation extends java.lang.Throwable {
+  }
+
+  public final class WebViewMethodCalledOnWrongThreadViolation extends android.os.strictmode.Violation {
+  }
+
+}
+
 package android.preference {
 
   public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -39625,7 +39717,9 @@
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
     method public int describeContents();
+    method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
     method public int getRoute();
+    method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
     method public int getSupportedRouteMask();
     method public boolean isMuted();
     method public void writeToParcel(android.os.Parcel, int);
@@ -39760,6 +39854,7 @@
     method public final void putExtras(android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void requestBluetoothAudio(java.lang.String);
     method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
     method public final void sendRemoteRttRequest();
     method public final void sendRttInitiationFailure(int);
@@ -39963,6 +40058,7 @@
     method public void onCanAddCallChanged(boolean);
     method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
     method public void onSilenceRinger();
+    method public final void requestBluetoothAudio(java.lang.String);
     method public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index da88ee3..3cef6b3 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -65,7 +65,7 @@
 TEST(IhUtilTest, Reader) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path));
 
     Reader r(tf.fd);
     string line;
@@ -82,7 +82,7 @@
 TEST(IhUtilTest, ReaderSmallBufSize) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path));
 
     Reader r(tf.fd, 5);
     string line;
@@ -99,7 +99,7 @@
 TEST(IhUtilTest, ReaderEmpty) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("", tf.path));
 
     Reader r(tf.fd);
     string line;
@@ -112,7 +112,7 @@
 TEST(IhUtilTest, ReaderMultipleEmptyLines) {
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile("\n\n", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("\n\n", tf.path));
 
     Reader r(tf.fd);
     string line;
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 2afa778..3fd2ed8 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -84,7 +84,7 @@
 
 TEST_F(FdBufferTest, ReadAndWrite) {
     std::string testdata = "FdBuffer test string";
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
     ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
     AssertBufferReadSuccessful(testdata.size());
     AssertBufferContent(testdata.c_str());
@@ -97,7 +97,7 @@
 
 TEST_F(FdBufferTest, ReadAndIterate) {
     std::string testdata = "FdBuffer test string";
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
     ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
 
     int i=0;
@@ -137,7 +137,7 @@
 TEST_F(FdBufferTest, ReadInStreamAndWrite) {
     std::string testdata = "simply test read in stream";
     std::string expected = HEAD + testdata;
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
@@ -166,7 +166,7 @@
 TEST_F(FdBufferTest, ReadInStreamAndWriteAllAtOnce) {
     std::string testdata = "child process flushes only after all data are read.";
     std::string expected = HEAD + testdata;
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
@@ -196,7 +196,7 @@
 }
 
 TEST_F(FdBufferTest, ReadInStreamEmpty) {
-    ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("", tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
@@ -260,7 +260,7 @@
 
 TEST_F(FdBufferTest, ReadInStreamTimeOut) {
     std::string testdata = "timeout test";
-    ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
 
     int pid = fork();
     ASSERT_TRUE(pid != -1);
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
index 84a2a82..ca94623 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -60,7 +60,7 @@
     }
 
     void writeToFdBuffer(string str) {
-        ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
+        ASSERT_TRUE(WriteStringToFile(str, tf.path));
         ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
         ASSERT_EQ(str.size(), buffer.size());
     }
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 25b05b2..649e908 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -99,7 +99,7 @@
     ReportRequestSet requests;
 
     ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path, false));
+    ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path));
 
     requests.setMainFd(STDOUT_FILENO);
 
@@ -173,7 +173,7 @@
     ReportRequestSet requests;
 
     ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
 
     requests.setMainFd(STDOUT_FILENO);
 
@@ -186,7 +186,7 @@
     TemporaryFile input;
     FileSection fs(NOOP_PARSER, input.path);
     ReportRequestSet requests;
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args;
     args.setAll(true);
@@ -205,7 +205,7 @@
     TemporaryFile input;
     FileSection fs(NOOP_PARSER, input.path);
     ReportRequestSet requests;
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args;
     args.setAll(true);
@@ -223,7 +223,7 @@
     ASSERT_TRUE(output1.fd != -1);
     ASSERT_TRUE(output2.fd != -1);
     ASSERT_TRUE(output3.fd != -1);
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args1, args2, args3;
     args1.setAll(true);
@@ -265,7 +265,7 @@
     ASSERT_TRUE(output2.fd != -1);
     ASSERT_TRUE(output3.fd != -1);
 
-    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+    ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
 
     IncidentReportArgs args1, args2, args3, args4;
     args1.setAll(true);
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 87d318b..929c3cc 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -38,7 +38,7 @@
     src/matchers/CombinationLogMatchingTracker.cpp \
     src/matchers/matcher_util.cpp \
     src/matchers/SimpleLogMatchingTracker.cpp \
-    src/metrics/CountAnomalyTracker.cpp \
+    src/anomaly/DiscreteAnomalyTracker.cpp \
     src/metrics/MetricProducer.cpp \
     src/metrics/EventMetricProducer.cpp \
     src/metrics/CountMetricProducer.cpp \
@@ -150,19 +150,20 @@
 LOCAL_SRC_FILES := \
     $(statsd_common_src) \
     tests/AnomalyMonitor_test.cpp \
-    tests/ConditionTracker_test.cpp \
+    tests/anomaly/AnomalyTracker_test.cpp \
     tests/ConfigManager_test.cpp \
     tests/indexed_priority_queue_test.cpp \
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
     tests/MetricsManager_test.cpp \
     tests/UidMap_test.cpp \
+    tests/condition/CombinationConditionTracker_test.cpp \
+    tests/condition/SimpleConditionTracker_test.cpp \
     tests/metrics/OringDurationTracker_test.cpp \
     tests/metrics/MaxDurationTracker_test.cpp \
     tests/metrics/CountMetricProducer_test.cpp \
     tests/metrics/EventMetricProducer_test.cpp
 
-
 LOCAL_STATIC_LIBRARIES := \
     libgmock
 
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 8910523..8c70bb5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -15,9 +15,9 @@
  */
 
 #include "Log.h"
+#include "statslog.h"
 
 #include "StatsLogProcessor.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "metrics/CountMetricProducer.h"
 #include "stats_util.h"
 
@@ -25,6 +25,14 @@
 #include <utils/Errors.h>
 
 using namespace android;
+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::make_unique;
 using std::unique_ptr;
 using std::vector;
@@ -33,6 +41,14 @@
 namespace os {
 namespace statsd {
 
+// for ConfigMetricsReport
+const int FIELD_ID_CONFIG_KEY = 1;
+const int FIELD_ID_METRICS = 2;
+const int FIELD_ID_UID_MAP = 3;
+// for ConfigKey
+const int FIELD_ID_UID = 1;
+const int FIELD_ID_NAME = 2;
+
 StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
                                      const std::function<void(const vector<uint8_t>&)>& pushLog)
     : mUidMap(uidMap), mPushLog(pushLog) {
@@ -48,6 +64,22 @@
         pair.second->onLogEvent(msg);
         flushIfNecessary(msg.GetTimestampNs(), pair.first, pair.second);
     }
+
+    // Hard-coded logic to update the isolated uid's in the uid-map.
+    // The field numbers need to be currently updated by hand with stats_events.proto
+    if (msg.GetTagId() == android::util::ISOLATED_UID_CHANGED) {
+        status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR;
+        bool is_create = msg.GetBool(3, &err);
+        auto parent_uid = int(msg.GetLong(1, &err2));
+        auto isolated_uid = int(msg.GetLong(2, &err3));
+        if (err == NO_ERROR && err2 == NO_ERROR && err3 == NO_ERROR) {
+            if (is_create) {
+                mUidMap->assignIsolatedUid(isolated_uid, parent_uid);
+            } else {
+                mUidMap->removeIsolatedUid(isolated_uid, parent_uid);
+            }
+        }
+    }
 }
 
 void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
@@ -70,27 +102,46 @@
     }
 }
 
-ConfigMetricsReport StatsLogProcessor::onDumpReport(const ConfigKey& key) {
-    ConfigMetricsReport report;
-
+vector<uint8_t> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
-        return report;
+        return vector<uint8_t>();
     }
 
-    auto set_key = report.mutable_config_key();
-    set_key->set_uid(key.GetUid());
-    set_key->set_name(key.GetName());
-    for (auto m : it->second->onDumpReport()) {
-        // Transfer the vector of StatsLogReport into a field
-        // TODO: perhaps we just have bytes being returned from onDumpReport and transfer bytes
-        auto dest = report.add_metrics();
-        *dest = m;
+    ProtoOutputStream proto;
+
+    // Fill in ConfigKey.
+    long long configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
+    proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid());
+    proto.write(FIELD_TYPE_STRING | FIELD_ID_NAME, key.GetName());
+    proto.end(configKeyToken);
+
+    // Fill in StatsLogReport's.
+    for (auto& m : it->second->onDumpReport()) {
+        // Add each vector of StatsLogReport into a repeated field.
+        proto.write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS,
+                    reinterpret_cast<char*>(m.get()->data()), m.get()->size());
     }
-    auto temp = mUidMap->getOutput(key);
-    report.mutable_uid_map()->Swap(&temp);
-    return report;
+
+    // Fill in UidMap.
+    auto uidMap = mUidMap->getOutput(key);
+    const int uidMapSize = uidMap.ByteSize();
+    char uidMapBuffer[uidMapSize];
+    uidMap.SerializeToArray(&uidMapBuffer[0], uidMapSize);
+    proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP, uidMapBuffer, uidMapSize);
+
+    vector<uint8_t> buffer(proto.size());
+    size_t pos = 0;
+    auto iter = proto.data();
+    while (iter.readBuffer() != NULL) {
+        size_t toRead = iter.currentToRead();
+        std::memcpy(&buffer[pos], iter.readBuffer(), toRead);
+        pos += toRead;
+        iter.rp()->move(toRead);
+    }
+
+    return buffer;
 }
 
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 0083827..f38d715 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -41,8 +41,7 @@
     void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
     void OnConfigRemoved(const ConfigKey& key);
 
-    // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
-    ConfigMetricsReport onDumpReport(const ConfigKey& key);
+    vector<uint8_t> onDumpReport(const ConfigKey& key);
 
     /* Request a flush through a binder call. */
     void flush();
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index e2ac623..d9207e9 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -55,6 +55,7 @@
     };
 };
 
+// TODO: Rename this file to AnomalyAlarmMonitor.
 /**
  * Manages alarms for Anomaly Detection.
  */
@@ -95,6 +96,15 @@
     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,
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
new file mode 100644
index 0000000..9c9bde9
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "DiscreteAnomalyTracker.h"
+
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DiscreteAnomalyTracker::DiscreteAnomalyTracker(const Alert& alert) : mAlert(alert) {
+    VLOG("DiscreteAnomalyTracker() called");
+    if (mAlert.number_of_buckets() <= 0) {
+        ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
+              (long long)mAlert.number_of_buckets());
+        return;
+    }
+    mPastBuckets.resize(mAlert.number_of_buckets());
+    reset(); // initialization
+}
+
+DiscreteAnomalyTracker::~DiscreteAnomalyTracker() {
+    VLOG("~DiscreteAnomalyTracker() called");
+}
+
+void DiscreteAnomalyTracker::reset() {
+    VLOG("reset() called.");
+    mPastBuckets.clear();
+    mPastBuckets.resize(mAlert.number_of_buckets());
+    mSumOverPastBuckets.clear();
+    mCurrentBucketIndex = -1;
+    mLastAlarmAtBucketIndex = -1;
+    mAnomalyDeclared = 0;
+}
+
+size_t DiscreteAnomalyTracker::index(int64_t bucketNum) {
+    return bucketNum % mAlert.number_of_buckets();
+}
+
+void DiscreteAnomalyTracker::addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues,
+                                               int64_t bucketIndex) {
+    VLOG("addPastBucket() called.");
+    if (bucketIndex <= mCurrentBucketIndex - mAlert.number_of_buckets()) {
+        ALOGE("Cannot add a past bucket %lld units in past", (long long)bucketIndex);
+        return;
+    }
+
+    // Empty out old mPastBuckets[i] values and update mSumOverPastBuckets.
+    if (bucketIndex - mCurrentBucketIndex >= mAlert.number_of_buckets()) {
+        mPastBuckets.clear();
+        mPastBuckets.resize(mAlert.number_of_buckets());
+        mSumOverPastBuckets.clear();
+    } else {
+        for (int64_t i = std::max(
+                     0LL, (long long)(mCurrentBucketIndex - mAlert.number_of_buckets() + 1));
+             i < bucketIndex - mAlert.number_of_buckets(); i++) {
+            const int idx = index(i);
+            subtractBucketFromSum(mPastBuckets[idx]);
+            mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
+        }
+    }
+    subtractBucketFromSum(mPastBuckets[index(bucketIndex)]);
+    mPastBuckets[index(bucketIndex)] = nullptr;  // release (but not clear) the old bucket.
+
+    // Replace the oldest bucket with the new bucket we are adding.
+    mPastBuckets[index(bucketIndex)] = BucketValues;
+    addBucketToSum(BucketValues);
+
+    mCurrentBucketIndex = std::max(mCurrentBucketIndex, bucketIndex);
+}
+
+void DiscreteAnomalyTracker::subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket) {
+    if (bucket == nullptr) {
+        return;
+    }
+    // For each dimension present in the bucket, subtract its value from its corresponding sum.
+    for (const auto& keyValuePair : *bucket) {
+        auto itr = mSumOverPastBuckets.find(keyValuePair.first);
+        if (itr == mSumOverPastBuckets.end()) {
+            continue;
+        }
+        itr->second -= keyValuePair.second;
+        // TODO: No need to look up the object twice like this. Use a var.
+        if (itr->second == 0) {
+            mSumOverPastBuckets.erase(itr);
+        }
+    }
+}
+
+void DiscreteAnomalyTracker::addBucketToSum(const shared_ptr<const DimToValMap>& bucket) {
+    if (bucket == nullptr) {
+        return;
+    }
+    // For each dimension present in the bucket, add its value to its corresponding sum.
+    for (const auto& keyValuePair : *bucket) {
+        mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
+    }
+}
+
+bool DiscreteAnomalyTracker::detectAnomaly() {
+    for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
+        if (mAlert.has_trigger_if_sum_gt() && itr->second > mAlert.trigger_if_sum_gt()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void DiscreteAnomalyTracker::declareAndDeclareAnomaly() {
+    if (detectAnomaly()) {
+        declareAnomaly();
+    }
+}
+
+void DiscreteAnomalyTracker::declareAnomaly() {
+    if (mLastAlarmAtBucketIndex >= 0 && mCurrentBucketIndex - mLastAlarmAtBucketIndex <=
+                                        (long long)mAlert.refractory_period_in_buckets()) {
+        VLOG("Skipping anomaly check since within refractory period");
+        return;
+    }
+    mAnomalyDeclared++;
+    // TODO(guardrail): Consider guarding against too short refractory periods.
+    mLastAlarmAtBucketIndex = mCurrentBucketIndex;
+
+    if (mAlert.has_incidentd_details()) {
+        const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
+        if (incident.has_alert_name()) {
+            ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
+                  incident.alert_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.");
+        }
+        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+    } else {
+        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
new file mode 100644
index 0000000..ed7d5d7
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
+#include "stats_util.h" // HashableDimensionKey and DimToValMap
+
+#include <memory> // unique_ptr
+#include <stdlib.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::shared_ptr;
+
+// This anomaly track assmues that all values are non-negative.
+class DiscreteAnomalyTracker {
+ public:
+    DiscreteAnomalyTracker(const Alert& alert);
+
+    virtual ~DiscreteAnomalyTracker();
+
+    // Adds a new bucket or updates an existing bucket.
+    // Bucket index starts from 0.
+    void addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues, int64_t bucketIndex);
+
+    // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
+    bool detectAnomaly();
+
+    // Informs incidentd about the detected alert.
+    void declareAnomaly();
+
+    // Detects the alert and informs the incidentd when applicable.
+    void declareAndDeclareAnomaly();
+
+private:
+    // statsd_config.proto Alert message that defines this tracker.
+    const Alert mAlert;
+
+    // The exisiting bucket list.
+    std::vector<shared_ptr<const DimToValMap>> mPastBuckets;
+
+    // Sum over all existing buckets cached in mPastBuckets.
+    DimToValMap mSumOverPastBuckets;
+
+    // Current bucket index of the current anomaly detection window. Bucket index starts from 0.
+    int64_t mCurrentBucketIndex = -1;
+
+    // The bucket index when the last anomaly was declared.
+    int64_t mLastAlarmAtBucketIndex = -1;
+
+    // The total number of declared anomalies.
+    int64_t mAnomalyDeclared = 0;
+
+    // Add the information in the given bucket to mSumOverPastBuckets.
+    void addBucketToSum(const shared_ptr<const DimToValMap>& bucket);
+
+    // Subtract the information in the given bucket from mSumOverPastBuckets
+    // and remove any items with value 0.
+    void subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket);
+
+    // Calculates the corresponding bucket index within the circular array.
+    size_t index(int64_t bucketNum);
+
+    // Resets all data. For use when all the data gets stale.
+    void reset();
+
+    FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
+    FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 953bcb3..41f5fca 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -115,49 +115,47 @@
             evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
 }
 
-bool CombinationConditionTracker::evaluateCondition(
+void CombinationConditionTracker::evaluateCondition(
         const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
         const std::vector<sp<ConditionTracker>>& mAllConditions,
         std::vector<ConditionState>& nonSlicedConditionCache,
-        std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
+        std::vector<bool>& conditionChangedCache) {
     // value is up to date.
     if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        return false;
+        return;
     }
 
     for (const int childIndex : mChildren) {
         if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
             const sp<ConditionTracker>& child = mAllConditions[childIndex];
             child->evaluateCondition(event, eventMatcherValues, mAllConditions,
-                                     nonSlicedConditionCache, nonSlicedChangedCache,
-                                     slicedConditionChanged);
+                                     nonSlicedConditionCache, conditionChangedCache);
         }
     }
 
-    ConditionState newCondition =
-            evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
+    if (!mSliced) {
+        ConditionState newCondition =
+                evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
 
-    bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
-    mNonSlicedConditionState = newCondition;
+        bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+        mNonSlicedConditionState = newCondition;
 
-    nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
+        nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
 
-    nonSlicedChangedCache[mIndex] = nonSlicedChanged;
-
-    if (mSliced) {
+        conditionChangedCache[mIndex] = nonSlicedChanged;
+    } else {
         for (const int childIndex : mChildren) {
             // If any of the sliced condition in children condition changes, the combination
             // condition may be changed too.
-            if (slicedConditionChanged[childIndex]) {
-                slicedConditionChanged[mIndex] = true;
+            if (conditionChangedCache[childIndex]) {
+                conditionChangedCache[mIndex] = true;
                 break;
             }
         }
+        nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
         ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
-              slicedConditionChanged[mIndex] == true);
+              conditionChangedCache[mIndex] == true);
     }
-
-    return nonSlicedChanged;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index dbdb3b7..3d2c6bb 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -35,12 +35,11 @@
               const std::unordered_map<std::string, int>& conditionNameIndexMap,
               std::vector<bool>& stack) override;
 
-    bool evaluateCondition(const LogEvent& event,
+    void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache,
-                           std::vector<bool>& slicedConditionMayChanged) override;
+                           std::vector<bool>& changedCache) override;
 
     void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index bb5ddeb..0ac7ef3 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -56,25 +56,20 @@
                       std::vector<bool>& stack) = 0;
 
     // evaluate current condition given the new event.
-    // return true if the condition state changed, false if the condition state is not changed.
     // event: the new log event
     // eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
     //                     event before ConditionTrackers, because ConditionTracker depends on
     //                     LogMatchingTrackers.
     // mAllConditions: the list of all ConditionTracker
     // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
-    // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
-    // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
-    //      Because sliced condition needs parameters to determine the value. So the sliced
-    //      condition is not pushed to metrics. We only inform the relevant metrics that the sliced
-    //      condition may have changed, and metrics should pull the conditions that they are
-    //      interested in.
-    virtual bool evaluateCondition(const LogEvent& event,
+    // conditionChanged: the bit map to record whether the condition has changed.
+    //                   If the condition has dimension, then any sub condition changes will report
+    //                   conditionChanged.
+    virtual void evaluateCondition(const LogEvent& event,
                                    const std::vector<MatchingState>& eventMatcherValues,
                                    const std::vector<sp<ConditionTracker>>& mAllConditions,
                                    std::vector<ConditionState>& conditionCache,
-                                   std::vector<bool>& nonSlicedConditionChanged,
-                                   std::vector<bool>& slicedConditionMayChanged) = 0;
+                                   std::vector<bool>& conditionChanged) = 0;
 
     // Return the current condition state.
     virtual ConditionState isConditionMet() {
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index b691faea..a694dbf 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -74,13 +74,21 @@
         mStopAllLogMatcherIndex = -1;
     }
 
-    mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
-                      simpleCondition.dimension().end());
+    mOutputDimension.insert(mOutputDimension.begin(), simpleCondition.dimension().begin(),
+                            simpleCondition.dimension().end());
 
-    if (mDimension.size() > 0) {
+    if (mOutputDimension.size() > 0) {
         mSliced = true;
     }
 
+    if (simpleCondition.initial_value() == SimpleCondition_InitialValue_FALSE) {
+        mInitialValue = ConditionState::kFalse;
+    } else {
+        mInitialValue = ConditionState::kUnknown;
+    }
+
+    mNonSlicedConditionState = mInitialValue;
+
     mInitialized = true;
 }
 
@@ -97,127 +105,166 @@
     return mInitialized;
 }
 
-void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+void print(map<HashableDimensionKey, int>& conditions, const string& name) {
     VLOG("%s DUMP:", name.c_str());
-
     for (const auto& pair : conditions) {
-        VLOG("\t%s %d", pair.first.c_str(), pair.second);
+        VLOG("\t%s : %d", pair.first.c_str(), pair.second);
     }
 }
 
-bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
+void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
+                                           std::vector<bool>& conditionChangedCache) {
+    // Unless the default condition is false, and there was nothing started, otherwise we have
+    // triggered a condition change.
+    conditionChangedCache[mIndex] =
+            (mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false
+                                                                                           : true;
+
+    // After StopAll, we know everything has stopped. From now on, default condition is false.
+    mInitialValue = ConditionState::kFalse;
+    mSlicedConditionState.clear();
+    conditionCache[mIndex] = ConditionState::kFalse;
+}
+
+void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
+                                                  bool matchStart,
+                                                  std::vector<ConditionState>& conditionCache,
+                                                  std::vector<bool>& conditionChangedCache) {
+    bool changed = false;
+    auto outputIt = mSlicedConditionState.find(outputKey);
+    ConditionState newCondition;
+    if (outputIt == mSlicedConditionState.end()) {
+        // We get a new output key.
+        newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
+        if (matchStart && mInitialValue != ConditionState::kTrue) {
+            mSlicedConditionState[outputKey] = 1;
+            changed = true;
+        } else if (mInitialValue != ConditionState::kFalse) {
+            // it's a stop and we don't have history about it.
+            // If the default condition is not false, it means this stop is valuable to us.
+            mSlicedConditionState[outputKey] = 0;
+            changed = true;
+        }
+    } else {
+        // we have history about this output key.
+        auto& startedCount = outputIt->second;
+        // assign the old value first.
+        newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+        if (matchStart) {
+            if (startedCount == 0) {
+                // This condition for this output key will change from false -> true
+                changed = true;
+            }
+
+            // it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated
+            // as 1 if not counting nesting.
+            startedCount++;
+            newCondition = ConditionState::kTrue;
+        } else {
+            // This is a stop event.
+            if (startedCount > 0) {
+                if (mCountNesting) {
+                    startedCount--;
+                    if (startedCount == 0) {
+                        newCondition = ConditionState::kFalse;
+                    }
+                } else {
+                    // not counting nesting, so ignore the number of starts, stop now.
+                    startedCount = 0;
+                    newCondition = ConditionState::kFalse;
+                }
+                // if everything has stopped for this output key, condition true -> false;
+                if (startedCount == 0) {
+                    changed = true;
+                }
+            }
+
+            // if default condition is false, it means we don't need to keep the false values.
+            if (mInitialValue == ConditionState::kFalse && startedCount == 0) {
+                mSlicedConditionState.erase(outputIt);
+                VLOG("erase key %s", outputKey.c_str());
+            }
+        }
+    }
+
+    // dump all dimensions for debugging
+    if (DEBUG) {
+        print(mSlicedConditionState, mName);
+    }
+
+    conditionChangedCache[mIndex] = changed;
+    conditionCache[mIndex] = newCondition;
+
+    VLOG("SimpleCondition %s nonSlicedChange? %d", mName.c_str(),
+         conditionChangedCache[mIndex] == true);
+}
+
+void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                                const vector<MatchingState>& eventMatcherValues,
                                                const vector<sp<ConditionTracker>>& mAllConditions,
                                                vector<ConditionState>& conditionCache,
-                                               vector<bool>& nonSlicedConditionChanged,
-                                               std::vector<bool>& slicedConditionChanged) {
+                                               vector<bool>& conditionChangedCache) {
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
         VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
-        return false;
+        return;
     }
 
-    // Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
+    if (mStopAllLogMatcherIndex >= 0 &&
+        eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+        handleStopAll(conditionCache, conditionChangedCache);
+        return;
+    }
 
-    ConditionState newCondition = mNonSlicedConditionState;
-    bool matched = false;
+    int matchedState = -1;
     // Note: The order to evaluate the following start, stop, stop_all matters.
     // The priority of overwrite is stop_all > stop > start.
     if (mStartLogMatcherIndex >= 0 &&
         eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
-        matched = true;
-        newCondition = ConditionState::kTrue;
+        matchedState = 1;
     }
 
     if (mStopLogMatcherIndex >= 0 &&
         eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
-        matched = true;
-        newCondition = ConditionState::kFalse;
+        matchedState = 0;
     }
 
-    bool stopAll = false;
-    if (mStopAllLogMatcherIndex >= 0 &&
-        eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
-        matched = true;
-        newCondition = ConditionState::kFalse;
-        stopAll = true;
-    }
-
-    if (matched == false) {
-        slicedConditionChanged[mIndex] = false;
-        nonSlicedConditionChanged[mIndex] = false;
+    if (matchedState < 0) {
+        conditionChangedCache[mIndex] = false;
         conditionCache[mIndex] = mNonSlicedConditionState;
-        return false;
+        return;
     }
 
-    bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
-
-    bool slicedChanged = false;
-
-    if (stopAll) {
-        // TODO: handle stop all; all dimension should be cleared.
-    }
-
-
-    if (mDimension.size() > 0) {
-        HashableDimensionKey hashableKey = getHashableKey(getDimensionKey(event, mDimension));
-        if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
-            mSlicedConditionState[hashableKey] != newCondition) {
-            slicedChanged = true;
-            mSlicedConditionState[hashableKey] = newCondition;
-        }
-        VLOG("key: %s %d", hashableKey.c_str(), newCondition);
-        // dump all dimensions for debugging
-        if (DEBUG) {
-            print(mSlicedConditionState, mName);
-        }
-    }
-
-    // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
-    // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
-    // so mark the slicedConditionChanged to be true.
-    // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
-    //     APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
-    //     SCREEN_OFF is not sliced, and it changes from False -> True;
-    //     We need to populate this change to parent condition. Because for App_B,
-    //     the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
-    slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
-    nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
-
-    VLOG("SimpleCondition %s nonSlicedChange? %d  SlicedChanged? %d", mName.c_str(),
-         nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
-    mNonSlicedConditionState = newCondition;
-    conditionCache[mIndex] = mNonSlicedConditionState;
-
-    return nonSlicedConditionChanged[mIndex];
+    // outputKey is the output key values. e.g, uid:1234
+    const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
+    handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
 }
 
 void SimpleConditionTracker::isConditionMet(
         const map<string, HashableDimensionKey>& conditionParameters,
         const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
     const auto pair = conditionParameters.find(mName);
-    if (pair == conditionParameters.end()) {
-        // the query does not need my sliced condition. just return the non sliced condition.
-        conditionCache[mIndex] = mNonSlicedConditionState;
-        VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+    HashableDimensionKey key =
+            (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second;
+
+    if (pair == conditionParameters.end() && mOutputDimension.size() > 0) {
+        ALOGE("Condition %s output has dimension, but it's not specified in the query!",
+              mName.c_str());
+        conditionCache[mIndex] = mInitialValue;
         return;
     }
 
-    const HashableDimensionKey& key = pair->second;
     VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
 
-    if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
-        // never seen this key before. the condition is unknown to us.
-        conditionCache[mIndex] = ConditionState::kUnknown;
+    auto startedCountIt = mSlicedConditionState.find(key);
+    if (startedCountIt == mSlicedConditionState.end()) {
+        conditionCache[mIndex] = mInitialValue;
     } else {
-        conditionCache[mIndex] = mSlicedConditionState[key];
+        conditionCache[mIndex] =
+                startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
     }
 
     VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
-
-    if (DEBUG) {
-        print(mSlicedConditionState, mName);
-    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index b72157b..2eda0b1 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -17,6 +17,7 @@
 #ifndef SIMPLE_CONDITION_TRACKER_H
 #define SIMPLE_CONDITION_TRACKER_H
 
+#include <gtest/gtest_prod.h>
 #include "ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
@@ -38,12 +39,11 @@
               const std::unordered_map<std::string, int>& conditionNameIndexMap,
               std::vector<bool>& stack) override;
 
-    bool evaluateCondition(const LogEvent& event,
+    void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache,
-                           std::vector<bool>& slicedChangedCache) override;
+                           std::vector<bool>& changedCache) override;
 
     void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
@@ -62,15 +62,22 @@
     // The index of the LogEventMatcher which defines the stop all.
     int mStopAllLogMatcherIndex;
 
-    // The dimension defines at the atom level, how start and stop should match.
-    // e.g., APP_IN_FOREGROUND, the dimension should be the uid field. Each "start" and
-    // "stop" tells you the state change of a particular app. Without this dimension, this
-    // condition does not make sense.
-    std::vector<KeyMatcher> mDimension;
+    ConditionState mInitialValue;
 
-    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
-    // that StatsLogReport wants.
-    std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
+    std::vector<KeyMatcher> mOutputDimension;
+
+    std::map<HashableDimensionKey, int> mSlicedConditionState;
+
+    void handleStopAll(std::vector<ConditionState>& conditionCache,
+                       std::vector<bool>& changedCache);
+
+    void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
+                              std::vector<ConditionState>& conditionCache,
+                              std::vector<bool>& changedCache);
+
+    FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition);
+    FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim);
+    FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index 40d41be..2618a21 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -25,7 +25,6 @@
 #include <unordered_map>
 #include "../matchers/matcher_util.h"
 #include "ConditionTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
 
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index 47e245e..4167bf9 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -19,7 +19,6 @@
 
 #include <vector>
 #include "../matchers/matcher_util.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
 namespace android {
diff --git a/cmds/statsd/src/config/ConfigListener.h b/cmds/statsd/src/config/ConfigListener.h
index a58766d..19ccfcf 100644
--- a/cmds/statsd/src/config/ConfigListener.h
+++ b/cmds/statsd/src/config/ConfigListener.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
 #include "config/ConfigKey.h"
 
 #include <utils/RefBase.h>
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 02e6903..d86ab57 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -165,6 +165,12 @@
     KeyMatcher* keyMatcher = metric->add_dimension();
     keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
 
+    // Anomaly threshold for background count.
+    alert = metric->add_alerts();
+    alert->set_number_of_buckets(4);
+    alert->set_trigger_if_sum_gt(30);
+    alert->set_refractory_period_secs(20);
+
     // Count process state changes, slice by uid, while SCREEN_IS_OFF
     metric = config.add_count_metric();
     metric->set_metric_id(3);
@@ -194,7 +200,7 @@
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
-    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
@@ -208,7 +214,7 @@
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
-    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
@@ -220,7 +226,7 @@
     durationMetric->set_metric_id(7);
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
-    durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+    durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
@@ -328,12 +334,14 @@
     SimpleCondition* simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("SCREEN_TURNED_ON");
     simpleCondition->set_stop("SCREEN_TURNED_OFF");
+    simpleCondition->set_count_nesting(false);
 
     condition = config.add_condition();
     condition->set_name("SCREEN_IS_OFF");
     simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("SCREEN_TURNED_OFF");
     simpleCondition->set_stop("SCREEN_TURNED_ON");
+    simpleCondition->set_count_nesting(false);
 
     condition = config.add_condition();
     condition->set_name("APP_IS_BACKGROUND");
@@ -342,6 +350,7 @@
     simpleCondition->set_stop("APP_GOES_FOREGROUND");
     KeyMatcher* condition_dimension1 = simpleCondition->add_dimension();
     condition_dimension1->set_key(APP_USAGE_UID_KEY_ID);
+    simpleCondition->set_count_nesting(false);
 
     condition = config.add_condition();
     condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
@@ -351,7 +360,7 @@
     combination_condition->add_condition("SCREEN_IS_ON");
 
     condition = config.add_condition();
-    condition->set_name("WL_STATE_PER_APP_PER_NAME");
+    condition->set_name("WL_HELD_PER_APP_PER_NAME");
     simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("APP_GET_WL");
     simpleCondition->set_stop("APP_RELEASE_WL");
@@ -359,6 +368,17 @@
     condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
     condition_dimension = simpleCondition->add_dimension();
     condition_dimension->set_key(WAKE_LOCK_NAME_KEY);
+    simpleCondition->set_count_nesting(true);
+
+    condition = config.add_condition();
+    condition->set_name("WL_HELD_PER_APP");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("APP_GET_WL");
+    simpleCondition->set_stop("APP_RELEASE_WL");
+    simpleCondition->set_initial_value(SimpleCondition_InitialValue_FALSE);
+    condition_dimension = simpleCondition->add_dimension();
+    condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+    simpleCondition->set_count_nesting(true);
 
     return config;
 }
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
index 006d74c..adb691e 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -22,7 +22,6 @@
 #include <unordered_map>
 #include <vector>
 #include "LogMatchingTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
 namespace android {
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index d82da3b..ffbf248 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -17,7 +17,6 @@
 #ifndef LOG_MATCHING_TRACKER_H
 #define LOG_MATCHING_TRACKER_H
 
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "logd/LogEvent.h"
 #include "matchers/matcher_util.h"
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
index e110ec8..5dca55e 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -23,7 +23,6 @@
 #include <unordered_map>
 #include <vector>
 #include "LogMatchingTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
 namespace android {
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 6aa2211..cccc9b3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -16,7 +16,6 @@
 
 #include "Log.h"
 
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/LogMatchingTracker.h"
 #include "matchers/matcher_util.h"
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
deleted file mode 100644
index 7aa748f..0000000
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG true  // STOPSHIP if true
-#include "Log.h"
-
-#include "CountAnomalyTracker.h"
-
-#include <time.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-CountAnomalyTracker::CountAnomalyTracker(const Alert& alert)
-    : mAlert(alert),
-      mNumPastBuckets(alert.number_of_buckets() > 0 ? alert.number_of_buckets() - 1 : 0),
-      mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr) {
-
-    VLOG("CountAnomalyTracker() called");
-    if (alert.number_of_buckets() < 1) {
-        ALOGE("Cannot create CountAnomalyTracker with %d buckets", alert.number_of_buckets());
-    }
-    reset(); // initialization
-}
-
-CountAnomalyTracker::~CountAnomalyTracker() {
-    VLOG("~CountAnomalyTracker() called");
-}
-
-void CountAnomalyTracker::addPastBucket(int pastBucketCount,
-                                        time_t numberOfBucketsAgo) {
-    VLOG("addPastBucket() called.");
-    if (numberOfBucketsAgo < 1) {
-        ALOGE("Cannot add a past bucket %ld units in past", numberOfBucketsAgo);
-        return;
-    }
-    // If past bucket was ancient, just empty out all past info.
-    // This always applies if mNumPastBuckets == 0 (i.e. store no past buckets).
-    if (numberOfBucketsAgo > (time_t) mNumPastBuckets) {
-        reset();
-        return;
-    }
-
-    // Empty out old mPastBuckets[i] values and update mSumPastCounters.
-    for (size_t i = mOldestBucketIndex;
-                        i < mOldestBucketIndex + numberOfBucketsAgo; i++) {
-        mSumPastCounters -= mPastBuckets[index(i)];
-        mPastBuckets[index(i)] = 0;
-    }
-
-    // Replace the oldest bucket with the new bucket we are adding.
-    mPastBuckets[mOldestBucketIndex] = pastBucketCount;
-    mSumPastCounters += pastBucketCount;
-
-    // Advance the oldest bucket index by numberOfBucketsAgo units.
-    mOldestBucketIndex = index(mOldestBucketIndex + numberOfBucketsAgo);
-
-    // TODO: Once dimensions are added to mSumPastCounters:
-    // iterate through mSumPastCounters and remove any entries that are 0.
-}
-
-void CountAnomalyTracker::reset() {
-    VLOG("reset() called.");
-    for (size_t i = 0; i < mNumPastBuckets; i++) {
-        mPastBuckets[i] = 0;
-    }
-    mSumPastCounters = 0;
-    mOldestBucketIndex = 0;
-}
-
-void CountAnomalyTracker::checkAnomaly(int currentCount) {
-    // Skip the check if in refractory period.
-    if (time(nullptr) < mRefractoryPeriodEndsSec) {
-        VLOG("Skipping anomaly check since within refractory period");
-        return;
-    }
-
-    // TODO: Remove these extremely verbose debugging log.
-    VLOG("Checking whether %d + %d > %lld",
-         mSumPastCounters, currentCount, mAlert.trigger_if_sum_gt());
-
-    // Note that this works even if mNumPastBuckets < 1 (since then
-    // mSumPastCounters = 0 so the comparison is based only on currentCount).
-    if (mAlert.has_trigger_if_sum_gt() &&
-            mSumPastCounters + currentCount > mAlert.trigger_if_sum_gt()) {
-        declareAnomaly();
-    }
-}
-
-void CountAnomalyTracker::declareAnomaly() {
-    // TODO(guardrail): Consider guarding against too short refractory periods.
-    time_t currTime = time(nullptr);
-    mRefractoryPeriodEndsSec = currTime + mAlert.refractory_period_secs();
-
-    // TODO: If we had access to the bucket_size_millis, consider calling reset()
-    // if (mAlert.refractory_period_secs() > mNumPastBuckets * bucket_size_millis * 1000).
-
-    if (mAlert.has_incidentd_details()) {
-        const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
-        if (incident.has_alert_name()) {
-            ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
-                  incident.alert_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.");
-        }
-        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
-    } else {
-        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.h b/cmds/statsd/src/metrics/CountAnomalyTracker.h
deleted file mode 100644
index 79c47d2a..0000000
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef COUNT_ANOMALY_TRACKER_H
-#define COUNT_ANOMALY_TRACKER_H
-
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
-
-#include <memory> // unique_ptr
-#include <stdlib.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// TODO: Can probably be used for Count, Value, and Gauge. If so, rename to ValueAnomalyTracker.
-// (caveat: currently, the value cannot be negative. Probably fine for P.)
-class CountAnomalyTracker {
-public:
-    CountAnomalyTracker(const Alert& alert);
-
-    virtual ~CountAnomalyTracker();
-
-
-    // Adds a new past bucket, holding pastBucketCount, and then advances the
-    // present by numberOfBucketsAgo buckets (filling any intervening buckets
-    // with 0s).
-    // Thus, the newly added bucket (which holds pastBucketCount) is stored
-    // numberOfBucketsAgo buckets ago.
-    void addPastBucket(int pastBucketCount, time_t numberOfBucketsAgo);
-
-    // Informs the anomaly tracker of the current bucket's count, so that it can
-    // determine whether an anomaly has occurred. This value is not stored.
-    void checkAnomaly(int currentCount);
-
-private:
-    // statsd_config.proto Alert message that defines this tracker.
-    const Alert mAlert;
-
-    // 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).
-    const size_t mNumPastBuckets;
-
-    // Count values for each of the past mNumPastBuckets buckets.
-    // TODO: Add dimensions. This parallels the type of CountMetricProducer.mCounter.
-    std::unique_ptr<int[]> mPastBuckets;
-
-    // Sum over all of mPastBuckets (cached).
-    // TODO: Add dimensions. This parallels the type of CountMetricProducer.mCounter.
-    //       At that point, mSumPastCounters must never contain entries of 0.
-    int mSumPastCounters;
-
-    // Index of the oldest bucket (i.e. the next bucket to be overwritten).
-    size_t mOldestBucketIndex = 0;
-
-    // Timestamp that the refractory period (if this anomaly was declared) ends, in seconds.
-    // If an anomaly was never declared, set to 0.
-    time_t mRefractoryPeriodEndsSec = 0;
-
-    void declareAnomaly();
-
-    // Calculates the corresponding index within the circular array.
-    size_t index(size_t unsafeIndex) {
-        return unsafeIndex % mNumPastBuckets;
-    }
-
-    // Resets all data. For use when all the data gets stale.
-    void reset();
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#endif  // COUNT_ANOMALY_TRACKER_H
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 94f4adf..2252201 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,13 +17,14 @@
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
-#include "CountAnomalyTracker.h"
+#include "../anomaly/DiscreteAnomalyTracker.h"
 #include "CountMetricProducer.h"
 #include "stats_util.h"
 
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
 using android::util::FIELD_TYPE_INT32;
@@ -61,6 +62,7 @@
 const int FIELD_ID_COUNT = 3;
 
 // TODO: add back AnomalyTracker.
+
 CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
                                          const sp<ConditionWizard>& wizard,
                                          const uint64_t startTimeNs)
@@ -76,7 +78,7 @@
     for (int i = 0; i < metric.alerts_size(); i++) {
         const Alert& alert = metric.alerts(i);
         if (alert.trigger_if_sum_gt() > 0 && alert.number_of_buckets() > 0) {
-            mAnomalyTrackers.push_back(std::make_unique<CountAnomalyTracker>(alert));
+            mAnomalyTrackers.push_back(std::make_unique<DiscreteAnomalyTracker>(alert));
         } else {
             ALOGW("Ignoring invalid count metric alert: threshold=%lld num_buckets= %d",
                   alert.trigger_if_sum_gt(), alert.number_of_buckets());
@@ -116,7 +118,7 @@
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
 }
 
-StatsLogReport CountMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
     long long endTime = time(nullptr) * NS_PER_SEC;
 
     // Dump current bucket if it's stale.
@@ -133,11 +135,13 @@
             ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
             continue;
         }
-        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+        long long wrapperToken =
+                mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
         for (const auto& kv : it->second) {
-            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            long long dimensionToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
                 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -153,7 +157,8 @@
 
         // Then fill bucket_info (CountBucketInfo).
         for (const auto& bucket : counter.second) {
-            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            long long bucketInfoToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
                           (long long)bucket.mBucketStartNs);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -171,15 +176,13 @@
                   (long long)mCurrentBucketStartTimeNs);
 
     VLOG("metric %lld dump report now...", mMetric.metric_id());
-    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+    std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
 
     startNewProtoOutputStream(endTime);
     mPastBuckets.clear();
     mByteSize = 0;
 
-    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
-    // return std::move(buffer);
-    return StatsLogReport();
+    return buffer;
 
     // TODO: Clear mDimensionKeyMap once the report is dumped.
 }
@@ -201,25 +204,19 @@
         return;
     }
 
-    auto it = mCurrentSlicedCounter.find(eventKey);
+    auto it = mCurrentSlicedCounter->find(eventKey);
 
-    if (it == mCurrentSlicedCounter.end()) {
+    if (it == mCurrentSlicedCounter->end()) {
         // create a counter for the new key
-        mCurrentSlicedCounter[eventKey] = 1;
-
+        (*mCurrentSlicedCounter)[eventKey] = 1;
     } else {
         // increment the existing value
         auto& count = it->second;
         count++;
     }
 
-    // TODO: Re-add anomaly detection (similar to):
-    // for (auto& tracker : mAnomalyTrackers) {
-    //     tracker->checkAnomaly(mCounter);
-    // }
-
     VLOG("metric %lld %s->%d", mMetric.metric_id(), eventKey.c_str(),
-         mCurrentSlicedCounter[eventKey]);
+         (*mCurrentSlicedCounter)[eventKey]);
 }
 
 // When a new matched event comes in, we check if event falls into the current
@@ -230,12 +227,13 @@
     }
 
     // adjust the bucket start time
-    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    // TODO: This (and addPastBucket to which it goes) doesn't really need to be an int64.
+    uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
 
     CountBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    for (const auto& counter : mCurrentSlicedCounter) {
+    for (const auto& counter : *mCurrentSlicedCounter) {
         info.mCount = counter.second;
         auto& bucketList = mPastBuckets[counter.first];
         bucketList.push_back(info);
@@ -244,15 +242,16 @@
         mByteSize += sizeof(info);
     }
 
-    // TODO: Re-add anomaly detection (similar to):
-    // for (auto& tracker : mAnomalyTrackers) {
-    //     tracker->addPastBucket(mCounter, numBucketsForward);
-    //}
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->addOrUpdateBucket(mCurrentSlicedCounter, mCurrentBucketNum);
+        tracker->declareAndDeclareAnomaly();
+    }
 
-    // Reset counters
-    mCurrentSlicedCounter.clear();
+    // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
+    mCurrentSlicedCounter = std::make_shared<DimToValMap>();
 
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
     VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
          (long long)mCurrentBucketStartTimeNs);
 }
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index c3af006..b7e480c 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -23,14 +23,11 @@
 #include <gtest/gtest_prod.h>
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
-#include "CountAnomalyTracker.h"
+#include "../anomaly/DiscreteAnomalyTracker.h"
 #include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
 
-using namespace std;
-
 namespace android {
 namespace os {
 namespace statsd {
@@ -54,7 +51,7 @@
     void finish() override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
-    StatsLogReport onDumpReport() override;
+    std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
@@ -82,9 +79,9 @@
     size_t mByteSize;
 
     // The current bucket.
-    std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+    std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
 
-    vector<unique_ptr<CountAnomalyTracker>> mAnomalyTrackers;
+    vector<std::unique_ptr<DiscreteAnomalyTracker>> mAnomalyTrackers;
 
     void flushCounterIfNeeded(const uint64_t newEventTime);
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index c0a0d98..74ce9cd 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -23,6 +23,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
 using android::util::FIELD_TYPE_INT32;
@@ -159,7 +160,7 @@
     }
 }
 
-StatsLogReport DurationMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
     long long endTime = time(nullptr) * NS_PER_SEC;
 
     // Dump current bucket if it's stale.
@@ -176,11 +177,13 @@
             ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
             continue;
         }
-        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+        long long wrapperToken =
+                mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
         for (const auto& kv : it->second) {
-            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            long long dimensionToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
                 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -196,7 +199,8 @@
 
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
-            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            long long bucketInfoToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
                           (long long)bucket.mBucketStartNs);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -214,14 +218,12 @@
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
                   (long long)mCurrentBucketStartTimeNs);
 
-    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+    std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
 
     startNewProtoOutputStream(endTime);
     mPastBuckets.clear();
 
-    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
-    // return std::move(buffer);
-    return StatsLogReport();
+    return buffer;
 }
 
 void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 8fdd0d4..eea00454 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -26,7 +26,6 @@
 #include "duration_helper/DurationTracker.h"
 #include "duration_helper/MaxDurationTracker.h"
 #include "duration_helper/OringDurationTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
 
@@ -50,7 +49,7 @@
     void finish() override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
-    StatsLogReport onDumpReport() override;
+    std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index ee0bfde..a65092f 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -23,6 +23,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
 using android::util::FIELD_TYPE_INT32;
@@ -84,20 +85,19 @@
 void EventMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
 }
 
-StatsLogReport EventMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> EventMetricProducer::onDumpReport() {
     long long endTime = time(nullptr) * NS_PER_SEC;
     mProto->end(mProtoToken);
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime);
 
     size_t bufferSize = mProto->size();
     VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize);
-    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+    std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
 
     startNewProtoOutputStream(endTime);
+    mByteSize = 0;
 
-    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
-    // return std::move(buffer);
-    return StatsLogReport();
+    return buffer;
 }
 
 void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
@@ -113,16 +113,18 @@
         return;
     }
 
-    long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+    long long wrapperToken =
+            mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_TIMESTAMP_NANOS, (long long)event.GetTimestampNs());
     long long eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_STATS_EVENTS);
     event.ToProto(*mProto);
     mProto->end(eventToken);
     mProto->end(wrapperToken);
+    // TODO: Find a proper way to derive the size of incoming LogEvent.
 }
 
 size_t EventMetricProducer::byteSize() {
-    return mProto->size();
+    return mByteSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 2ca8181..7740621 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -24,7 +24,6 @@
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
 
@@ -50,7 +49,7 @@
     void finish() override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
-    StatsLogReport onDumpReport() override;
+    std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
@@ -66,6 +65,8 @@
 
 private:
     const EventMetric mMetric;
+
+    size_t mByteSize;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index dcfcc19..fafeee4 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -24,6 +24,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
 using android::util::FIELD_TYPE_INT32;
@@ -106,7 +107,7 @@
 void GaugeMetricProducer::finish() {
 }
 
-StatsLogReport GaugeMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
     VLOG("gauge metric %lld dump report now...", mMetric.metric_id());
 
     // Dump current bucket if it's stale.
@@ -123,11 +124,13 @@
         }
 
         VLOG("  dimension key %s", hashableKey.c_str());
-        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+        long long wrapperToken =
+                mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
         for (const auto& kv : it->second) {
-            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            long long dimensionToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
                 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -143,7 +146,8 @@
 
         // Then fill bucket_info (GaugeBucketInfo).
         for (const auto& bucket : pair.second) {
-            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            long long bucketInfoToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
                           (long long)bucket.mBucketStartNs);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -159,15 +163,13 @@
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
                   (long long)mCurrentBucketStartTimeNs);
 
-    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+    std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
 
     startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
     mPastBuckets.clear();
     mByteSize = 0;
 
-    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
-    // return std::move(buffer);
-    return StatsLogReport();
+    return buffer;
 
     // TODO: Clear mDimensionKeyMap once the report is dumped.
 }
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 3757174..d80672d 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -24,7 +24,6 @@
 #include "../external/StatsPullerManager.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
 
@@ -60,7 +59,7 @@
     void finish() override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
-    StatsLogReport onDumpReport() override;
+    std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
     size_t byteSize() override;
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 5ca83b2..62fb632 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -64,16 +64,16 @@
                               scheduledPull);
 }
 
-std::unique_ptr<uint8_t[]> MetricProducer::serializeProto() {
+std::unique_ptr<std::vector<uint8_t>> MetricProducer::serializeProto() {
     size_t bufferSize = mProto->size();
 
-    std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+    std::unique_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>(bufferSize));
 
     size_t pos = 0;
     auto it = mProto->data();
     while (it.readBuffer() != NULL) {
         size_t toRead = it.currentToRead();
-        std::memcpy(&buffer[pos], it.readBuffer(), toRead);
+        std::memcpy(&((*buffer)[pos]), it.readBuffer(), toRead);
         pos += toRead;
         it.rp()->move(toRead);
     }
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 7ac97a8..c0930e3 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -23,7 +23,6 @@
 
 #include <log/logprint.h>
 #include <utils/RefBase.h>
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 
 namespace android {
 namespace os {
@@ -39,6 +38,7 @@
                    const sp<ConditionWizard>& wizard)
         : mStartTimeNs(startTimeNs),
           mCurrentBucketStartTimeNs(startTimeNs),
+          mCurrentBucketNum(0),
           mCondition(conditionIndex >= 0 ? false : true),
           mConditionSliced(false),
           mWizard(wizard),
@@ -61,7 +61,7 @@
 
     // TODO: Pass a timestamp as a parameter in onDumpReport and update all its
     // implementations.
-    virtual StatsLogReport onDumpReport() = 0;
+    virtual std::unique_ptr<std::vector<uint8_t>> onDumpReport() = 0;
 
     virtual bool isConditionSliced() const {
         return mConditionSliced;
@@ -74,6 +74,8 @@
 
     uint64_t mCurrentBucketStartTimeNs;
 
+    uint64_t mCurrentBucketNum;
+
     int64_t mBucketSizeNs;
 
     bool mCondition;
@@ -118,7 +120,7 @@
 
     virtual void startNewProtoOutputStream(long long timestamp) = 0;
 
-    std::unique_ptr<uint8_t[]> serializeProto();
+    std::unique_ptr<std::vector<uint8_t>> serializeProto();
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 80b325f..e8a862f 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -56,10 +56,10 @@
     }
 }
 
-vector<StatsLogReport> MetricsManager::onDumpReport() {
+vector<std::unique_ptr<vector<uint8_t>>> MetricsManager::onDumpReport() {
     VLOG("=========================Metric Reports Start==========================");
     // one StatsLogReport per MetricProduer
-    vector<StatsLogReport> reportList;
+    vector<std::unique_ptr<vector<uint8_t>>> reportList;
     for (auto& metric : mAllMetricProducers) {
         reportList.push_back(metric->onDumpReport());
     }
@@ -80,8 +80,6 @@
         return;
     }
 
-    // Since at least one of the metrics is interested in this event, we parse it now.
-    ALOGD("%s", event.ToString().c_str());
     vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
 
     for (auto& matcher : mAllLogEntryMatchers) {
@@ -104,18 +102,17 @@
                                           ConditionState::kNotEvaluated);
     // A bitmap to track if a condition has changed value.
     vector<bool> changedCache(mAllConditionTrackers.size(), false);
-    vector<bool> slicedChangedCache(mAllConditionTrackers.size(), false);
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
         if (conditionToBeEvaluated[i] == false) {
             continue;
         }
         sp<ConditionTracker>& condition = mAllConditionTrackers[i];
         condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
-                                     changedCache, slicedChangedCache);
+                                     changedCache);
     }
 
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
-        if (changedCache[i] == false && slicedChangedCache[i] == false) {
+        if (changedCache[i] == false) {
             continue;
         }
         auto pair = mConditionToMetricMap.find(i);
@@ -124,14 +121,13 @@
             for (auto metricIndex : metricList) {
                 // metric cares about non sliced condition, and it's changed.
                 // Push the new condition to it directly.
-                if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
+                if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
                     mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
                                                                          eventTime);
                     // metric cares about sliced conditions, and it may have changed. Send
                     // notification, and the metric can query the sliced conditions that are
                     // interesting to it.
-                } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
-                           slicedChangedCache[i]) {
+                } else if (mAllMetricProducers[metricIndex]->isConditionSliced()) {
                     mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(eventTime);
                 }
             }
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 63e2c33..39c79f9 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -44,7 +44,7 @@
     void finish();
 
     // Config source owner can call onDumpReport() to get all the metrics collected.
-    std::vector<StatsLogReport> onDumpReport();
+    std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
 
     size_t byteSize();
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 41b7a5d..e6e63f7 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -23,6 +23,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
 using android::util::FIELD_TYPE_INT32;
@@ -108,7 +109,7 @@
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
 }
 
-StatsLogReport ValueMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() {
     VLOG("metric %lld dump report now...", mMetric.metric_id());
 
     for (const auto& pair : mPastBuckets) {
@@ -119,11 +120,13 @@
             ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
             continue;
         }
-        long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+        long long wrapperToken =
+                mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension (KeyValuePairs).
         for (const auto& kv : it->second) {
-            long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+            long long dimensionToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
                 mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -139,7 +142,8 @@
 
         // Then fill bucket_info (ValueBucketInfo).
         for (const auto& bucket : pair.second) {
-            long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+            long long bucketInfoToken =
+                    mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
                           (long long)bucket.mBucketStartNs);
             mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -156,15 +160,13 @@
                   (long long)mCurrentBucketStartTimeNs);
 
     VLOG("metric %lld dump report now...", mMetric.metric_id());
-    std::unique_ptr<uint8_t[]> buffer = serializeProto();
+    std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
 
     startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
     mPastBuckets.clear();
     mByteSize = 0;
 
-    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
-    // return std::move(buffer);
-    return StatsLogReport();
+    return buffer;
 
     // TODO: Clear mDimensionKeyMap once the report is dumped.
 }
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 8437665..ef9868b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -21,9 +21,7 @@
 #include "../condition/ConditionTracker.h"
 #include "../external/PullDataReceiver.h"
 #include "../external/StatsPullerManager.h"
-#include "CountAnomalyTracker.h"
 #include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
 namespace android {
@@ -49,7 +47,7 @@
     void finish() override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
-    StatsLogReport onDumpReport() override;
+    std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
     void onSlicedConditionMayChange(const uint64_t eventTime);
 
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index aeb234d..5c76d0e 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -20,8 +20,6 @@
 #include "condition/ConditionWizard.h"
 #include "stats_util.h"
 
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index de53d62..b095884 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -19,8 +19,6 @@
 
 #include "DurationTracker.h"
 
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index d83c3a4..2398814 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -52,7 +52,7 @@
 
 void UidMap::updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
                        const vector<String16>& packageName) {
-    updateMap(time(nullptr) * 1000000000, uid, versionCode, packageName);
+    updateMap(time(nullptr) * NS_PER_SEC, uid, versionCode, packageName);
 }
 
 void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
@@ -76,7 +76,7 @@
 }
 
 void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
-    updateApp(time(nullptr) * 1000000000, app_16, uid, versionCode);
+    updateApp(time(nullptr) * NS_PER_SEC, app_16, uid, versionCode);
 }
 
 void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
@@ -112,7 +112,7 @@
 }
 
 void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
-    removeApp(time(nullptr) * 1000000000, app_16, uid);
+    removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
 }
 void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
     lock_guard<mutex> lock(mMutex);
@@ -150,6 +150,31 @@
     mSubscribers.erase(producer);
 }
 
+void UidMap::assignIsolatedUid(int isolatedUid, int parentUid) {
+    lock_guard<mutex> lock(mIsolatedMutex);
+
+    mIsolatedUidMap[isolatedUid] = parentUid;
+}
+
+void UidMap::removeIsolatedUid(int isolatedUid, int parentUid) {
+    lock_guard<mutex> lock(mIsolatedMutex);
+
+    auto it = mIsolatedUidMap.find(isolatedUid);
+    if (it != mIsolatedUidMap.end()) {
+        mIsolatedUidMap.erase(it);
+    }
+}
+
+int UidMap::getParentUidOrSelf(int uid) {
+    lock_guard<mutex> lock(mIsolatedMutex);
+
+    auto it = mIsolatedUidMap.find(uid);
+    if (it != mIsolatedUidMap.end()) {
+        return it->second;
+    }
+    return uid;
+}
+
 void UidMap::clearOutput() {
     mOutput.Clear();
 
@@ -177,7 +202,7 @@
 }
 
 UidMapping UidMap::getOutput(const ConfigKey& key) {
-    return getOutput(time(nullptr) * 1000000000, key);
+    return getOutput(time(nullptr) * NS_PER_SEC, key);
 }
 
 UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index bf120e0..de68fbc 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -84,6 +84,12 @@
     // Informs uid map that a config is removed. Used for keeping mConfigKeys up to date.
     void OnConfigRemoved(const ConfigKey& key);
 
+    void assignIsolatedUid(int isolatedUid, int parentUid);
+    void removeIsolatedUid(int isolatedUid, int parentUid);
+
+    // Returns the parent uid if it exists. Otherwise, returns the same uid that was passed-in.
+    int getParentUidOrSelf(int uid);
+
     // Gets the output. If every config key has received the output, then the output is cleared.
     UidMapping getOutput(const ConfigKey& key);
 
@@ -105,11 +111,16 @@
 
     // TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
     mutable mutex mMutex;
+    mutable mutex mIsolatedMutex;
 
     // Maps uid to application data. This must be multimap since there is a feature in Android for
     // multiple apps to share the same uid.
     std::unordered_multimap<int, AppData> mMap;
 
+    // Maps isolated uid to the parent uid. Any metrics for an isolated uid will instead contribute
+    // to the parent uid.
+    std::unordered_map<int, int> mIsolatedUidMap;
+
     // We prepare the output proto as apps are updated, so that we can grab the current output.
     UidMapping mOutput;
 
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index 82d9759..8816795 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -72,7 +72,7 @@
         PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
         SettingChanged setting_changed = 41;
         ActivityForegroundStateChanged activity_foreground_state_changed = 42;
-
+        IsolatedUidChanged isolated_uid_changed = 43;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
@@ -695,7 +695,6 @@
     optional int32 user = 7;
 }
 
-
 /*
  * Logs activity going to foreground or background
  *
@@ -848,3 +847,22 @@
     optional uint64 last_entry_timestamp_ms = 5;
     optional bool supported_only_in_suspend = 6;
 }
+
+/**
+ * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky
+ * behavior in its own uid. However, the metrics of these isolated uid's almost always should be
+ * attributed back to the parent (host) uid. One example is Chrome.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message IsolatedUidChanged {
+    // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid.
+    optional int32 parent_uid = 1;
+
+    optional int32 isolated_uid = 2;
+
+    // 1 denotes we're creating an isolated uid and 0 denotes removal. We expect an isolated uid to
+    // be removed before if it's used for another parent uid.
+    optional int32 is_create = 3;
+}
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index a9507bf..e1d0aceb 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef STATS_UTIL_H
-#define STATS_UTIL_H
+
+#pragma once
 
 #include "logd/LogReader.h"
 #include "storage/DropboxWriter.h"
@@ -22,6 +22,8 @@
 #include <log/logprint.h>
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
+#include <unordered_map>
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -33,6 +35,10 @@
 
 typedef std::map<std::string, HashableDimensionKey> ConditionKey;
 
+// TODO: For P, change int to int64_t.
+// TODO: Should HashableDimensionKey be marked here as const?
+typedef std::unordered_map<HashableDimensionKey, int> DimToValMap;
+
 EventMetricData parse(log_msg msg);
 
 int getTagId(log_msg msg);
@@ -41,5 +47,3 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
-#endif  // STATS_UTIL_H
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index f3e6894..a07f76a 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -83,7 +83,13 @@
 
   optional string stop_all = 4;
 
-  repeated KeyMatcher dimension = 5;
+  enum InitialValue {
+    UNKNOWN = 0;
+    FALSE = 1;
+  }
+  optional InitialValue initial_value = 5 [default = UNKNOWN];
+
+  repeated KeyMatcher dimension = 6;
 }
 
 message Condition {
@@ -117,6 +123,8 @@
   optional int32 refractory_period_secs = 4;
 
   optional int64 trigger_if_sum_gt = 5;
+
+  optional int32 refractory_period_in_buckets = 6;
 }
 
 message EventMetric {
@@ -168,7 +176,6 @@
   repeated Alert alerts = 7;
 
   repeated EventConditionLink links = 8;
-
 }
 
 message GaugeMetric {
@@ -235,4 +242,6 @@
   repeated LogEntryMatcher log_entry_matcher = 7;
 
   repeated Condition condition = 8;
+
+  repeated Alert alerts = 9;
 }
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 671f6d4..c64719e 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -13,7 +13,10 @@
 // limitations under the License.
 
 #include "packages/UidMap.h"
+#include "StatsLogProcessor.h"
 #include "config/ConfigKey.h"
+#include "logd/LogEvent.h"
+#include "statslog.h"
 
 #include <gtest/gtest.h>
 
@@ -29,6 +32,31 @@
 const string kApp1 = "app1.sharing.1";
 const string kApp2 = "app2.sharing.1";
 
+TEST(UidMapTest, TestIsolatedUID) {
+    sp<UidMap> m = new UidMap();
+    StatsLogProcessor p(m, nullptr);
+    LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
+    android_log_event_list* list = addEvent.GetAndroidLogEventList();
+    *list << 100;  // parent UID
+    *list << 101;  // isolated UID
+    *list << 1;    // Indicates creation.
+    addEvent.init();
+
+    EXPECT_EQ(101, m->getParentUidOrSelf(101));
+
+    p.OnLogEvent(addEvent);
+    EXPECT_EQ(100, m->getParentUidOrSelf(101));
+
+    LogEvent removeEvent(android::util::ISOLATED_UID_CHANGED, 1);
+    list = removeEvent.GetAndroidLogEventList();
+    *list << 100;  // parent UID
+    *list << 101;  // isolated UID
+    *list << 0;    // Indicates removal.
+    removeEvent.init();
+    p.OnLogEvent(removeEvent);
+    EXPECT_EQ(101, m->getParentUidOrSelf(101));
+}
+
 TEST(UidMapTest, TestMatching) {
     UidMap m;
     vector<int32_t> uids;
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
new file mode 100644
index 0000000..b8150d0
--- /dev/null
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -0,0 +1,239 @@
+// 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/anomaly/DiscreteAnomalyTracker.h"
+
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair_list,
+                      std::shared_ptr<DimToValMap> bucket) {
+    for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
+        (*bucket)[itr->first] += itr->second;
+    }
+}
+
+std::shared_ptr<DimToValMap> MockeBucket(
+        const std::vector<std::pair<string, long>>& key_value_pair_list) {
+    std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
+    AddValueToBucket(key_value_pair_list, bucket);
+    return bucket;
+}
+
+TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
+    Alert alert;
+    alert.set_number_of_buckets(3);
+    alert.set_refractory_period_in_buckets(3);
+    alert.set_trigger_if_sum_gt(2);
+
+    DiscreteAnomalyTracker anomaly_tracker(alert);
+
+    std::shared_ptr<DimToValMap> bucket0 = MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+    // Adds bucket #0
+    anomaly_tracker.addOrUpdateBucket(bucket0, 0);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+
+    // Adds bucket #0 again. The sum does not change.
+    anomaly_tracker.addOrUpdateBucket(bucket0, 0);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 0L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+
+    // Adds bucket #1.
+    std::shared_ptr<DimToValMap> bucket1 = MockeBucket({{"b", 2}});
+    anomaly_tracker.addOrUpdateBucket(bucket1, 1);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    // Alarm.
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+    // Adds bucket #1 again. The sum does not change.
+    anomaly_tracker.addOrUpdateBucket(bucket1, 1);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    // Alarm.
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+    // Adds bucket #2.
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 2);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 2L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Within refractory period.
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+    // Adds bucket #3.
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 3);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 3L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+
+    // Adds bucket #3.
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 2}}), 4);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 4L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Within refractory period.
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 5);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 5L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Within refractory period.
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 5L);
+}
+
+TEST(AnomalyTrackerTest, TestSparseBuckets) {
+    Alert alert;
+    alert.set_number_of_buckets(3);
+    alert.set_refractory_period_in_buckets(3);
+    alert.set_trigger_if_sum_gt(2);
+
+    DiscreteAnomalyTracker anomaly_tracker(alert);
+
+    // Add bucket #9
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}}), 9);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 9L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+
+    // Add bucket #16
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 4}}), 16);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 16L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+
+    // Add bucket #18
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    // Within refractory period.
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+
+    // Add bucket #18 again.
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+
+    // Add bucket #20
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 3}, {"d", 1}}), 20);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 20L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+
+    // Add bucket #25
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"d", 1}}), 25);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 25L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1L);
+    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+
+    // Add bucket #28
+    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"e", 5}}), 28);
+    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 28L);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("e")->second, 5L);
+    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 3L);
+    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 28L);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/ConditionTracker_test.cpp b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
similarity index 98%
rename from cmds/statsd/tests/ConditionTracker_test.cpp
rename to cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
index 2935ac7..23d6926 100644
--- a/cmds/statsd/tests/ConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
@@ -23,7 +23,6 @@
 using namespace android::os::statsd;
 using std::vector;
 
-
 #ifdef __ANDROID__
 TEST(ConditionTrackerTest, TestUnknownCondition) {
     LogicalOperation operation = LogicalOperation::AND;
@@ -39,7 +38,7 @@
     conditionResults.push_back(ConditionState::kTrue);
 
     EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults),
-            ConditionState::kUnknown);
+              ConditionState::kUnknown);
 }
 TEST(ConditionTrackerTest, TestAndCondition) {
     // Set up the matcher
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
new file mode 100644
index 0000000..05aad29
--- /dev/null
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -0,0 +1,469 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "src/condition/SimpleConditionTracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using std::map;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SimpleCondition getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
+                                         bool outputSlicedUid) {
+    SimpleCondition simpleCondition;
+    simpleCondition.set_start("WAKE_LOCK_ACQUIRE");
+    simpleCondition.set_stop("WAKE_LOCK_RELEASE");
+    simpleCondition.set_stop_all("RELEASE_ALL");
+    if (outputSlicedUid) {
+        KeyMatcher* keyMatcher = simpleCondition.add_dimension();
+        keyMatcher->set_key(1);
+    }
+
+    simpleCondition.set_count_nesting(countNesting);
+    simpleCondition.set_initial_value(defaultFalse ? SimpleCondition_InitialValue_FALSE
+                                                       : SimpleCondition_InitialValue_UNKNOWN);
+    return simpleCondition;
+}
+
+void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) {
+    auto list = event->GetAndroidLogEventList();
+    *list << uid;  // uid
+    *list << wl;
+    *list << acquire;
+    event->init();
+}
+
+map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid,
+                                                      const string& conditionName) {
+    // test query
+    KeyValuePair kv1;
+    kv1.set_key(key);
+    kv1.set_value_int(uid);
+    vector<KeyValuePair> kv_list;
+    kv_list.push_back(kv1);
+    map<string, HashableDimensionKey> queryKey;
+    queryKey[conditionName] = getHashableKey(kv_list);
+    return queryKey;
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
+    SimpleCondition simpleCondition;
+    simpleCondition.set_start("SCREEN_TURNED_ON");
+    simpleCondition.set_stop("SCREEN_TURNED_OFF");
+    simpleCondition.set_count_nesting(false);
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
+    trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+
+    SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*tracker index*/, simpleCondition,
+                                            trackerNameIndexMap);
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // not matched start or stop. condition doesn't change
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // prepare a case for match start.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // now condition should change to true.
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // the case for match stop.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    // condition changes to false.
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // match stop again.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // condition should still be false. not changed.
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
+    SimpleCondition simpleCondition;
+    simpleCondition.set_start("SCREEN_TURNED_ON");
+    simpleCondition.set_stop("SCREEN_TURNED_OFF");
+    simpleCondition.set_count_nesting(true);
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
+    trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+
+    SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+
+    // one matched start
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+
+    // prepare for another matched start.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // ONE MATCHED STOP
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // result should still be true
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+    EXPECT_FALSE(changedCache[0]);
+
+    // ANOTHER MATCHED STOP
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // result should still be true
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+    EXPECT_TRUE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
+    SimpleCondition simpleCondition = getWakeLockHeldCondition(
+            true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
+    string conditionName = "WL_HELD_BY_UID2";
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+    trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+    int uid = 111;
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event, uid, "wl1", 1);
+
+    // one matched start
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // Now test query
+    const auto queryKey = getWakeLockQueryKey(1, uid, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+    // another wake lock acquired by this uid
+    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event2, uid, "wl2", 1);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_FALSE(changedCache[0]);
+
+    // wake lock 1 release
+    LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event3, uid, "wl1", 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // nothing changes, because wake lock 2 is still held for this uid
+    EXPECT_FALSE(changedCache[0]);
+
+    LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event4, uid, "wl2", 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event4, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // query again
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
+    SimpleCondition simpleCondition = getWakeLockHeldCondition(
+            true /*nesting*/, true /*default to false*/, false /*slice output by uid*/);
+    string conditionName = "WL_HELD";
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+    trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+    int uid1 = 111;
+    string uid1_wl1 = "wl1_1";
+    int uid2 = 222;
+    string uid2_wl1 = "wl2_1";
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event, uid1, uid1_wl1, 1);
+
+    // one matched start for uid1
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // Now test query
+    map<string, HashableDimensionKey> queryKey;
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+    // another wake lock acquired by this uid
+    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event2, uid2, uid2_wl1, 1);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_FALSE(changedCache[0]);
+
+    // uid1 wake lock 1 release
+    LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event3, uid1, uid1_wl1, 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    // nothing changes, because uid2 is still holding wl.
+    EXPECT_FALSE(changedCache[0]);
+
+    LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event4, uid2, uid2_wl1, 0);  // now release it.
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event4, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // query again
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestStopAll) {
+    SimpleCondition simpleCondition = getWakeLockHeldCondition(
+            true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
+    string conditionName = "WL_HELD_BY_UID3";
+
+    unordered_map<string, int> trackerNameIndexMap;
+    trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+    trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+    trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+    SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+                                            simpleCondition, trackerNameIndexMap);
+    int uid1 = 111;
+    int uid2 = 222;
+
+    LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event, uid1, "wl1", 1);
+
+    // one matched start
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    vector<sp<ConditionTracker>> allConditions;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+                                       changedCache);
+
+    EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // Now test query
+    const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+    // another wake lock acquired by uid2
+    LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+    makeWakeLockEvent(&event2, uid2, "wl2", 1);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    // TEST QUERY
+    const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+
+    // stop all event
+    LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
+    matcherState.clear();
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kNotMatched);
+    matcherState.push_back(MatchingState::kMatched);
+
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    changedCache[0] = false;
+    conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+                                       changedCache);
+    EXPECT_TRUE(changedCache[0]);
+    EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+
+    // TEST QUERY
+    const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+
+    // TEST QUERY
+    const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName);
+    conditionCache[0] = ConditionState::kNotEvaluated;
+
+    conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e260967..d49e11f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -418,22 +419,51 @@
      * different process.  In addition, if the given Intent resolves to
      * multiple activities, instead of displaying a dialog for the user to
      * select an activity, an exception will be thrown.
-     * 
+     *
      * <p>The function returns as soon as the activity goes idle following the
      * call to its {@link Activity#onCreate}.  Generally this means it has gone
      * through the full initialization including {@link Activity#onResume} and
      * drawn and displayed its initial window.
-     * 
+     *
      * @param intent Description of the activity to start.
-     * 
+     *
      * @see Context#startActivity
+     * @see #startActivitySync(Intent, Bundle)
      */
     public Activity startActivitySync(Intent intent) {
+        return startActivitySync(intent, null /* options */);
+    }
+
+    /**
+     * Start a new activity and wait for it to begin running before returning.
+     * In addition to being synchronous, this method as some semantic
+     * differences from the standard {@link Context#startActivity} call: the
+     * activity component is resolved before talking with the activity manager
+     * (its class name is specified in the Intent that this method ultimately
+     * starts), and it does not allow you to start activities that run in a
+     * different process.  In addition, if the given Intent resolves to
+     * multiple activities, instead of displaying a dialog for the user to
+     * select an activity, an exception will be thrown.
+     *
+     * <p>The function returns as soon as the activity goes idle following the
+     * call to its {@link Activity#onCreate}.  Generally this means it has gone
+     * through the full initialization including {@link Activity#onResume} and
+     * drawn and displayed its initial window.
+     *
+     * @param intent Description of the activity to start.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.  See {@link android.app.ActivityOptions}
+     * for how to build the Bundle supplied here; there are no supported definitions
+     * for building it manually.
+     *
+     * @see Context#startActivity(Intent, Bundle)
+     */
+    public Activity startActivitySync(Intent intent, @Nullable Bundle options) {
         validateNotAppThread();
 
         synchronized (mSync) {
             intent = new Intent(intent);
-    
+
             ActivityInfo ai = intent.resolveActivityInfo(
                 getTargetContext().getPackageManager(), 0);
             if (ai == null) {
@@ -447,7 +477,7 @@
                         + myProc + " resolved to different process "
                         + ai.processName + ": " + intent);
             }
-    
+
             intent.setComponent(new ComponentName(
                     ai.applicationInfo.packageName, ai.name));
             final ActivityWaiter aw = new ActivityWaiter(intent);
@@ -457,7 +487,7 @@
             }
             mWaitingActivities.add(aw);
 
-            getTargetContext().startActivity(intent);
+            getTargetContext().startActivity(intent, options);
 
             do {
                 try {
@@ -465,7 +495,7 @@
                 } catch (InterruptedException e) {
                 }
             } while (mWaitingActivities.contains(aw));
-         
+
             return aw.activity;
         }
     }
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index b640bd5..530d84b 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -16,6 +16,12 @@
 
 package android.app.job;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.util.TimeUtils.formatDuration;
 
 import android.annotation.BytesLong;
@@ -25,6 +31,8 @@
 import android.annotation.RequiresPermission;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.Uri;
 import android.os.BaseBundle;
 import android.os.Bundle;
@@ -56,6 +64,7 @@
             NETWORK_TYPE_ANY,
             NETWORK_TYPE_UNMETERED,
             NETWORK_TYPE_NOT_ROAMING,
+            NETWORK_TYPE_CELLULAR,
             NETWORK_TYPE_METERED,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -69,8 +78,21 @@
     public static final int NETWORK_TYPE_UNMETERED = 2;
     /** This job requires network connectivity that is not roaming. */
     public static final int NETWORK_TYPE_NOT_ROAMING = 3;
-    /** This job requires metered connectivity such as most cellular data networks. */
-    public static final int NETWORK_TYPE_METERED = 4;
+    /** This job requires network connectivity that is a cellular network. */
+    public static final int NETWORK_TYPE_CELLULAR = 4;
+
+    /**
+     * This job requires metered connectivity such as most cellular data
+     * networks.
+     *
+     * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be
+     *             metered, so this isn't a good way of selecting a specific
+     *             transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or
+     *             {@link android.net.NetworkRequest.Builder#addTransportType(int)}
+     *             if your job requires a specific network transport.
+     */
+    @Deprecated
+    public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR;
 
     /** Sentinel value indicating that bytes are unknown. */
     public static final int NETWORK_BYTES_UNKNOWN = -1;
@@ -253,7 +275,7 @@
     private final long triggerContentMaxDelay;
     private final boolean hasEarlyConstraint;
     private final boolean hasLateConstraint;
-    private final int networkType;
+    private final NetworkRequest networkRequest;
     private final long networkBytes;
     private final long minLatencyMillis;
     private final long maxExecutionDelayMillis;
@@ -385,10 +407,37 @@
     }
 
     /**
-     * The kind of connectivity requirements that the job has.
+     * Return the basic description of the kind of network this job requires.
+     *
+     * @deprecated This method attempts to map {@link #getRequiredNetwork()}
+     *             into the set of simple constants, which results in a loss of
+     *             fidelity. Callers should move to using
+     *             {@link #getRequiredNetwork()} directly.
+     * @see Builder#setRequiredNetworkType(int)
      */
+    @Deprecated
     public @NetworkType int getNetworkType() {
-        return networkType;
+        if (networkRequest == null) {
+            return NETWORK_TYPE_NONE;
+        } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+            return NETWORK_TYPE_UNMETERED;
+        } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+            return NETWORK_TYPE_NOT_ROAMING;
+        } else if (networkRequest.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            return NETWORK_TYPE_CELLULAR;
+        } else {
+            return NETWORK_TYPE_ANY;
+        }
+    }
+
+    /**
+     * Return the detailed description of the kind of network this job requires,
+     * or {@code null} if no specific kind of network is required.
+     *
+     * @see Builder#setRequiredNetwork(NetworkRequest)
+     */
+    public @Nullable NetworkRequest getRequiredNetwork() {
+        return networkRequest;
     }
 
     /**
@@ -438,8 +487,7 @@
      * job does not recur periodically.
      */
     public long getIntervalMillis() {
-        final long minInterval = getMinPeriodMillis();
-        return intervalMillis >= minInterval ? intervalMillis : minInterval;
+        return intervalMillis;
     }
 
     /**
@@ -447,10 +495,7 @@
      * execute at any time in a window of flex length at the end of the period.
      */
     public long getFlexMillis() {
-        long interval = getIntervalMillis();
-        long percentClamp = 5 * interval / 100;
-        long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
-        return clampedFlex <= interval ? clampedFlex : interval;
+        return flexMillis;
     }
 
     /**
@@ -459,8 +504,7 @@
      * to 30 seconds, minimum is currently 10 seconds.
      */
     public long getInitialBackoffMillis() {
-        final long minBackoff = getMinBackoffMillis();
-        return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff;
+        return initialBackoffMillis;
     }
 
     /**
@@ -538,7 +582,7 @@
         if (hasLateConstraint != j.hasLateConstraint) {
             return false;
         }
-        if (networkType != j.networkType) {
+        if (!Objects.equals(networkRequest, j.networkRequest)) {
             return false;
         }
         if (networkBytes != j.networkBytes) {
@@ -601,7 +645,9 @@
         hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay);
         hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
         hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
-        hashCode = 31 * hashCode + networkType;
+        if (networkRequest != null) {
+            hashCode = 31 * hashCode + networkRequest.hashCode();
+        }
         hashCode = 31 * hashCode + Long.hashCode(networkBytes);
         hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
         hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
@@ -632,7 +678,11 @@
         triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
         triggerContentUpdateDelay = in.readLong();
         triggerContentMaxDelay = in.readLong();
-        networkType = in.readInt();
+        if (in.readInt() != 0) {
+            networkRequest = NetworkRequest.CREATOR.createFromParcel(in);
+        } else {
+            networkRequest = null;
+        }
         networkBytes = in.readLong();
         minLatencyMillis = in.readLong();
         maxExecutionDelayMillis = in.readLong();
@@ -661,7 +711,7 @@
                 : null;
         triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
         triggerContentMaxDelay = b.mTriggerContentMaxDelay;
-        networkType = b.mNetworkType;
+        networkRequest = b.mNetworkRequest;
         networkBytes = b.mNetworkBytes;
         minLatencyMillis = b.mMinLatencyMillis;
         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -699,7 +749,12 @@
         out.writeTypedArray(triggerContentUris, flags);
         out.writeLong(triggerContentUpdateDelay);
         out.writeLong(triggerContentMaxDelay);
-        out.writeInt(networkType);
+        if (networkRequest != null) {
+            out.writeInt(1);
+            networkRequest.writeToParcel(out, flags);
+        } else {
+            out.writeInt(0);
+        }
         out.writeLong(networkBytes);
         out.writeLong(minLatencyMillis);
         out.writeLong(maxExecutionDelayMillis);
@@ -833,7 +888,7 @@
         private int mFlags;
         // Requirements.
         private int mConstraintFlags;
-        private int mNetworkType;
+        private NetworkRequest mNetworkRequest;
         private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
         private ArrayList<TriggerContentUri> mTriggerContentUris;
         private long mTriggerContentUpdateDelay = -1;
@@ -934,24 +989,84 @@
         }
 
         /**
-         * Set some description of the kind of network type your job needs to
-         * have. Not calling this function means the network is not necessary,
-         * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
-         * calling this function defines network as a strict requirement for
-         * your job. If the network requested is not available your job will
-         * never run. See {@link #setOverrideDeadline(long)} to change this
-         * behaviour.
+         * Set basic description of the kind of network your job requires. If
+         * you need more precise control over network capabilities, see
+         * {@link #setRequiredNetwork(NetworkRequest)}.
+         * <p>
+         * If your job doesn't need a network connection, you don't need to call
+         * this method, as the default value is {@link #NETWORK_TYPE_NONE}.
+         * <p>
+         * Calling this method defines network as a strict requirement for your
+         * job. If the network requested is not available your job will never
+         * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+         * Calling this method will override any requirements previously defined
+         * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
+         * want to call one of these methods.
          * <p class="note">
-         * Note: When your job executes in
+         * When your job executes in
          * {@link JobService#onStartJob(JobParameters)}, be sure to use the
          * specific network returned by {@link JobParameters#getNetwork()},
          * otherwise you'll use the default network which may not meet this
          * constraint.
          *
+         * @see #setRequiredNetwork(NetworkRequest)
+         * @see JobInfo#getNetworkType()
          * @see JobParameters#getNetwork()
          */
         public Builder setRequiredNetworkType(@NetworkType int networkType) {
-            mNetworkType = networkType;
+            if (networkType == NETWORK_TYPE_NONE) {
+                return setRequiredNetwork(null);
+            } else {
+                final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+                // All types require validated Internet
+                builder.addCapability(NET_CAPABILITY_INTERNET);
+                builder.addCapability(NET_CAPABILITY_VALIDATED);
+                builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+
+                if (networkType == NETWORK_TYPE_ANY) {
+                    // No other capabilities
+                } else if (networkType == NETWORK_TYPE_UNMETERED) {
+                    builder.addCapability(NET_CAPABILITY_NOT_METERED);
+                } else if (networkType == NETWORK_TYPE_NOT_ROAMING) {
+                    builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
+                } else if (networkType == NETWORK_TYPE_CELLULAR) {
+                    builder.addTransportType(TRANSPORT_CELLULAR);
+                }
+
+                return setRequiredNetwork(builder.build());
+            }
+        }
+
+        /**
+         * Set detailed description of the kind of network your job requires.
+         * <p>
+         * If your job doesn't need a network connection, you don't need to call
+         * this method, as the default is {@code null}.
+         * <p>
+         * Calling this method defines network as a strict requirement for your
+         * job. If the network requested is not available your job will never
+         * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+         * Calling this method will override any requirements previously defined
+         * by {@link #setRequiredNetworkType(int)}; you typically only want to
+         * call one of these methods.
+         * <p class="note">
+         * When your job executes in
+         * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+         * specific network returned by {@link JobParameters#getNetwork()},
+         * otherwise you'll use the default network which may not meet this
+         * constraint.
+         *
+         * @param networkRequest The detailed description of the kind of network
+         *            this job requires, or {@code null} if no specific kind of
+         *            network is required. Defining a {@link NetworkSpecifier}
+         *            is only supported for jobs that aren't persisted.
+         * @see #setRequiredNetworkType(int)
+         * @see JobInfo#getRequiredNetwork()
+         * @see JobParameters#getNetwork()
+         */
+        public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) {
+            mNetworkRequest = networkRequest;
             return this;
         }
 
@@ -1140,6 +1255,21 @@
          *                   higher.
          */
         public Builder setPeriodic(long intervalMillis, long flexMillis) {
+            final long minPeriod = getMinPeriodMillis();
+            if (intervalMillis < minPeriod) {
+                Log.w(TAG, "Requested interval " + formatDuration(intervalMillis) + " for job "
+                        + mJobId + " is too small; raising to " + formatDuration(minPeriod));
+                intervalMillis = minPeriod;
+            }
+
+            final long percentClamp = 5 * intervalMillis / 100;
+            final long minFlex = Math.max(percentClamp, getMinFlexMillis());
+            if (flexMillis < minFlex) {
+                Log.w(TAG, "Requested flex " + formatDuration(flexMillis) + " for job " + mJobId
+                        + " is too small; raising to " + formatDuration(minFlex));
+                flexMillis = minFlex;
+            }
+
             mIsPeriodic = true;
             mIntervalMillis = intervalMillis;
             mFlexMillis = flexMillis;
@@ -1189,6 +1319,13 @@
          */
         public Builder setBackoffCriteria(long initialBackoffMillis,
                 @BackoffPolicy int backoffPolicy) {
+            final long minBackoff = getMinBackoffMillis();
+            if (initialBackoffMillis < minBackoff) {
+                Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job "
+                        + mJobId + " is too small; raising to " + formatDuration(minBackoff));
+                initialBackoffMillis = minBackoff;
+            }
+
             mBackoffPolicySet = true;
             mInitialBackoffMillis = initialBackoffMillis;
             mBackoffPolicy = backoffPolicy;
@@ -1213,16 +1350,22 @@
         public JobInfo build() {
             // Allow jobs with no constraints - What am I, a database?
             if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
-                    mNetworkType == NETWORK_TYPE_NONE &&
+                    mNetworkRequest == null &&
                     mTriggerContentUris == null) {
                 throw new IllegalArgumentException("You're trying to build a job with no " +
                         "constraints, this is not allowed.");
             }
             // Check that network estimates require network type
-            if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+            if (mNetworkBytes > 0 && mNetworkRequest == null) {
                 throw new IllegalArgumentException(
                         "Can't provide estimated network usage without requiring a network");
             }
+            // We can't serialize network specifiers
+            if (mIsPersisted && mNetworkRequest != null
+                    && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+                throw new IllegalArgumentException(
+                        "Network specifiers aren't supported for persistent jobs");
+            }
             // Check that a deadline was not set on a periodic job.
             if (mIsPeriodic) {
                 if (mMaxExecutionDelayMillis != 0L) {
@@ -1257,31 +1400,7 @@
                         " back-off policy, so calling setBackoffCriteria with" +
                         " setRequiresDeviceIdle is an error.");
             }
-            JobInfo job = new JobInfo(this);
-            if (job.isPeriodic()) {
-                if (job.intervalMillis != job.getIntervalMillis()) {
-                    StringBuilder builder = new StringBuilder();
-                    builder.append("Specified interval for ")
-                            .append(String.valueOf(mJobId))
-                            .append(" is ");
-                    formatDuration(mIntervalMillis, builder);
-                    builder.append(". Clamped to ");
-                    formatDuration(job.getIntervalMillis(), builder);
-                    Log.w(TAG, builder.toString());
-                }
-                if (job.flexMillis != job.getFlexMillis()) {
-                    StringBuilder builder = new StringBuilder();
-                    builder.append("Specified flex for ")
-                            .append(String.valueOf(mJobId))
-                            .append(" is ");
-                    formatDuration(mFlexMillis, builder);
-                    builder.append(". Clamped to ");
-                    formatDuration(job.getFlexMillis(), builder);
-                    Log.w(TAG, builder.toString());
-                }
-            }
-            return job;
+            return new JobInfo(this);
         }
     }
-
 }
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 4035ee1..0569913 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -375,7 +375,7 @@
             IBluetooth bluetoothProxy =
                     BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
             if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
-            mPfd = bluetoothProxy.connectSocket(mDevice, mType,
+            mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
                     mUuid, mPort, getSecurityFlags());
             synchronized (this) {
                 if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
@@ -417,7 +417,7 @@
             return -1;
         }
         try {
-            mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
+            mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
                     mUuid, mPort, getSecurityFlags());
         } catch (RemoteException e) {
             Log.e(TAG, Log.getStackTraceString(new Throwable()));
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index f8c139f..2d490a0 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -22,6 +22,7 @@
 import android.database.CrossProcessCursorWrapper;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.DeadObjectException;
@@ -102,8 +103,16 @@
                 if (sAnrHandler == null) {
                     sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
                 }
+
+                // If the remote process hangs, we're going to kill it, so we're
+                // technically okay doing blocking calls.
+                Binder.allowBlocking(mContentProvider.asBinder());
             } else {
                 mAnrRunnable = null;
+
+                // If we're no longer watching for hangs, revert back to default
+                // blocking behavior.
+                Binder.defaultBlocking(mContentProvider.asBinder());
             }
         }
     }
@@ -511,6 +520,10 @@
     private boolean closeInternal() {
         mCloseGuard.close();
         if (mClosed.compareAndSet(false, true)) {
+            // We can't do ANR checks after we cease to exist! Reset any
+            // blocking behavior changes we might have made.
+            setDetectNotResponding(0);
+
             if (mStable) {
                 return mContentResolver.releaseProvider(mContentProvider);
             } else {
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index dfd3bbf..26efda1 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2105,6 +2105,7 @@
                 break;
             case DENSITY_DPI_NONE:
                 parts.add("nodpi");
+                break;
             default:
                 parts.add(config.densityDpi + "dpi");
                 break;
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 864a9bb..4d6ba28 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -20,6 +20,8 @@
 <p>The version of SQLite depends on the version of Android. See the following table:
 <table style="width:auto;">
   <tr><th>Android API</th><th>SQLite Version</th></tr>
+  <tr><td>API 27</td><td>3.19</td></tr>
+  <tr><td>API 26</td><td>3.18</td></tr>
   <tr><td>API 24</td><td>3.9</td></tr>
   <tr><td>API 21</td><td>3.8</td></tr>
   <tr><td>API 11</td><td>3.7</td></tr>
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2bfb013..b5bcd02 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -193,6 +193,19 @@
     }
 
     /**
+     * Reset the given interface back to the default blocking behavior,
+     * reverting any changes made by {@link #allowBlocking(IBinder)}.
+     *
+     * @hide
+     */
+    public static IBinder defaultBlocking(IBinder binder) {
+        if (binder instanceof BinderProxy) {
+            ((BinderProxy) binder).mWarnOnBlocking = sWarnOnBlocking;
+        }
+        return binder;
+    }
+
+    /**
      * Inherit the current {@link #allowBlocking(IBinder)} value from one given
      * interface to another.
      *
diff --git a/core/java/android/os/ParcelFileDescriptor.aidl b/core/java/android/os/ParcelFileDescriptor.aidl
index 5857aae..6bbd99e 100644
--- a/core/java/android/os/ParcelFileDescriptor.aidl
+++ b/core/java/android/os/ParcelFileDescriptor.aidl
@@ -17,4 +17,4 @@
 
 package android.os;
 
-parcelable ParcelFileDescriptor;
+parcelable ParcelFileDescriptor cpp_header "android/os/parcel_file_descriptor.h";
diff --git a/core/java/android/os/ParcelUuid.aidl b/core/java/android/os/ParcelUuid.aidl
index f7e080a..6f36297 100644
--- a/core/java/android/os/ParcelUuid.aidl
+++ b/core/java/android/os/ParcelUuid.aidl
@@ -16,4 +16,4 @@
 
 package android.os;
 
-parcelable ParcelUuid;
+parcelable ParcelUuid cpp_header "android/os/parcel_uuid.h";
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index d5820b6..f90604a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -16,11 +16,11 @@
 package android.os;
 
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -29,8 +29,6 @@
 import android.content.pm.ApplicationInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.os.StrictMode.ThreadPolicy;
-import android.os.StrictMode.VmPolicy;
 import android.os.strictmode.CleartextNetworkViolation;
 import android.os.strictmode.ContentUriWithoutPermissionViolation;
 import android.os.strictmode.CustomViolation;
@@ -44,7 +42,7 @@
 import android.os.strictmode.ResourceMismatchViolation;
 import android.os.strictmode.ServiceConnectionLeakedViolation;
 import android.os.strictmode.SqliteObjectLeakedViolation;
-import android.os.strictmode.UnbufferedIOViolation;
+import android.os.strictmode.UnbufferedIoViolation;
 import android.os.strictmode.UntaggedSocketViolation;
 import android.os.strictmode.Violation;
 import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
@@ -75,6 +73,8 @@
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -163,12 +163,14 @@
     private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
 
     /**
-     * Quick feature-flag that can be used to disable the defaults provided by
-     * {@link #initThreadDefaults(ApplicationInfo)} and
-     * {@link #initVmDefaults(ApplicationInfo)}.
+     * Quick feature-flag that can be used to disable the defaults provided by {@link
+     * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
      */
     private static final boolean DISABLE = false;
 
+    // Only apply VM penalties for the same violation at this interval.
+    private static final long MIN_VM_INTERVAL_MS = 1000;
+
     // Only log a duplicate stack trace to the logs every second.
     private static final long MIN_LOG_INTERVAL_MS = 1000;
 
@@ -374,6 +376,28 @@
 
     private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
 
+    private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
+            new ThreadLocal<>();
+    private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
+
+    /**
+     * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+     * provided executor when a Thread violation occurs.
+     */
+    public interface OnThreadViolationListener {
+        /** Called on a thread policy violation. */
+        void onThreadViolation(Violation v);
+    }
+
+    /**
+     * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+     * provided executor when a VM violation occurs.
+     */
+    public interface OnVmViolationListener {
+        /** Called on a VM policy violation. */
+        void onVmViolation(Violation v);
+    }
+
     /** {@hide} */
     @TestApi
     public static void setViolationLogger(ViolationLogger listener) {
@@ -403,12 +427,16 @@
      */
     public static final class ThreadPolicy {
         /** The default, lax policy which doesn't catch anything. */
-        public static final ThreadPolicy LAX = new ThreadPolicy(0);
+        public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
 
         final int mask;
+        final OnThreadViolationListener mListener;
+        final Executor mCallbackExecutor;
 
-        private ThreadPolicy(int mask) {
+        private ThreadPolicy(int mask, OnThreadViolationListener listener, Executor executor) {
             this.mask = mask;
+            mListener = listener;
+            mCallbackExecutor = executor;
         }
 
         @Override
@@ -436,6 +464,8 @@
          */
         public static final class Builder {
             private int mMask = 0;
+            private OnThreadViolationListener mListener;
+            private Executor mExecutor;
 
             /**
              * Create a Builder that detects nothing and has no violations. (but note that {@link
@@ -601,6 +631,20 @@
                 return enable(PENALTY_DROPBOX);
             }
 
+            /**
+             * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+             * executor every violation.
+             */
+            public Builder penaltyListener(
+                    @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
+                if (executor == null) {
+                    throw new NullPointerException("executor must not be null");
+                }
+                mListener = listener;
+                mExecutor = executor;
+                return this;
+            }
+
             private Builder enable(int bit) {
                 mMask |= bit;
                 return this;
@@ -620,7 +664,8 @@
             public ThreadPolicy build() {
                 // If there are detection bits set but no violation bits
                 // set, enable simple logging.
-                if (mMask != 0
+                if (mListener == null
+                        && mMask != 0
                         && (mMask
                                         & (PENALTY_DEATH
                                                 | PENALTY_LOG
@@ -629,7 +674,7 @@
                                 == 0) {
                     penaltyLog();
                 }
-                return new ThreadPolicy(mMask);
+                return new ThreadPolicy(mMask, mListener, mExecutor);
             }
         }
     }
@@ -641,19 +686,27 @@
      */
     public static final class VmPolicy {
         /** The default, lax policy which doesn't catch anything. */
-        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
+        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
 
         final int mask;
+        final OnVmViolationListener mListener;
+        final Executor mCallbackExecutor;
 
         // Map from class to max number of allowed instances in memory.
         final HashMap<Class, Integer> classInstanceLimit;
 
-        private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
+        private VmPolicy(
+                int mask,
+                HashMap<Class, Integer> classInstanceLimit,
+                OnVmViolationListener listener,
+                Executor executor) {
             if (classInstanceLimit == null) {
                 throw new NullPointerException("classInstanceLimit == null");
             }
             this.mask = mask;
             this.classInstanceLimit = classInstanceLimit;
+            mListener = listener;
+            mCallbackExecutor = executor;
         }
 
         @Override
@@ -681,6 +734,8 @@
          */
         public static final class Builder {
             private int mMask;
+            private OnVmViolationListener mListener;
+            private Executor mExecutor;
 
             private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
             private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
@@ -694,6 +749,8 @@
                 mMask = base.mask;
                 mClassInstanceLimitNeedCow = true;
                 mClassInstanceLimit = base.classInstanceLimit;
+                mListener = base.mListener;
+                mExecutor = base.mCallbackExecutor;
             }
 
             /**
@@ -910,6 +967,19 @@
                 return enable(PENALTY_DROPBOX);
             }
 
+            /**
+             * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+             */
+            public Builder penaltyListener(
+                    @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
+                if (executor == null) {
+                    throw new NullPointerException("executor must not be null");
+                }
+                mListener = listener;
+                mExecutor = executor;
+                return this;
+            }
+
             private Builder enable(int bit) {
                 mMask |= bit;
                 return this;
@@ -929,7 +999,8 @@
             public VmPolicy build() {
                 // If there are detection bits set but no violation bits
                 // set, enable simple logging.
-                if (mMask != 0
+                if (mListener == null
+                        && mMask != 0
                         && (mMask
                                         & (PENALTY_DEATH
                                                 | PENALTY_LOG
@@ -940,7 +1011,9 @@
                 }
                 return new VmPolicy(
                         mMask,
-                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
+                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
+                        mListener,
+                        mExecutor);
             }
         }
     }
@@ -973,6 +1046,8 @@
      */
     public static void setThreadPolicy(final ThreadPolicy policy) {
         setThreadPolicyMask(policy.mask);
+        sThreadViolationListener.set(policy.mListener);
+        sThreadViolationExecutor.set(policy.mCallbackExecutor);
     }
 
     /** @hide */
@@ -1029,7 +1104,10 @@
         // introduce VmPolicy cleanly) but this isn't particularly
         // optimal for users who might call this method often.  This
         // should be in a thread-local and not allocate on each call.
-        return new ThreadPolicy(getThreadPolicyMask());
+        return new ThreadPolicy(
+                getThreadPolicyMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
     }
 
     /**
@@ -1042,7 +1120,10 @@
      *     end of a block
      */
     public static ThreadPolicy allowThreadDiskWrites() {
-        return new ThreadPolicy(allowThreadDiskWritesMask());
+        return new ThreadPolicy(
+                allowThreadDiskWritesMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
     }
 
     /** @hide */
@@ -1063,7 +1144,10 @@
      * @return the old policy, to be passed to setThreadPolicy to restore the policy.
      */
     public static ThreadPolicy allowThreadDiskReads() {
-        return new ThreadPolicy(allowThreadDiskReadsMask());
+        return new ThreadPolicy(
+                allowThreadDiskReadsMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
     }
 
     /** @hide */
@@ -1076,16 +1160,27 @@
         return oldPolicyMask;
     }
 
+    private static ThreadPolicy allowThreadViolations() {
+        ThreadPolicy oldPolicy = getThreadPolicy();
+        setThreadPolicyMask(0);
+        return oldPolicy;
+    }
+
+    private static VmPolicy allowVmViolations() {
+        VmPolicy oldPolicy = getVmPolicy();
+        sVmPolicy = VmPolicy.LAX;
+        return oldPolicy;
+    }
+
     /**
-     * Determine if the given app is "bundled" as part of the system image.
-     * These bundled apps are developed in lock-step with the OS, and they
-     * aren't updated outside of an OTA, so we want to chase any
-     * {@link StrictMode} regressions by enabling detection when running on
-     * {@link Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
-     * <p>
-     * Unbundled apps included in the system image are expected to detect and
-     * triage their own {@link StrictMode} issues separate from the OS release
-     * process, which is why we don't enable them here.
+     * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+     * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
+     * chase any {@link StrictMode} regressions by enabling detection when running on {@link
+     * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
+     *
+     * <p>Unbundled apps included in the system image are expected to detect and triage their own
+     * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
+     * them here.
      *
      * @hide
      */
@@ -1122,8 +1217,8 @@
      */
     public static void initThreadDefaults(ApplicationInfo ai) {
         final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
-        final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion
-                : Build.VERSION_CODES.CUR_DEVELOPMENT;
+        final int targetSdkVersion =
+                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
 
         // Starting in HC, we don't allow network usage on the main thread
         if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
@@ -1162,8 +1257,8 @@
      */
     public static void initVmDefaults(ApplicationInfo ai) {
         final VmPolicy.Builder builder = new VmPolicy.Builder();
-        final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion
-                : Build.VERSION_CODES.CUR_DEVELOPMENT;
+        final int targetSdkVersion =
+                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
 
         // Starting in N, we don't allow file:// Uri exposure
         if (targetSdkVersion >= Build.VERSION_CODES.N) {
@@ -1204,7 +1299,9 @@
                         sVmPolicy.mask
                                 | DETECT_VM_FILE_URI_EXPOSURE
                                 | PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
-                        sVmPolicy.classInstanceLimit);
+                        sVmPolicy.classInstanceLimit,
+                        sVmPolicy.mListener,
+                        sVmPolicy.mCallbackExecutor);
     }
 
     /**
@@ -1219,7 +1316,9 @@
                         sVmPolicy.mask
                                 & ~(DETECT_VM_FILE_URI_EXPOSURE
                                         | PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
-                        sVmPolicy.classInstanceLimit);
+                        sVmPolicy.classInstanceLimit,
+                        sVmPolicy.mListener,
+                        sVmPolicy.mCallbackExecutor);
     }
 
     /**
@@ -1340,7 +1439,7 @@
             if (tooManyViolationsThisLoop()) {
                 return;
             }
-            startHandlingViolationException(new UnbufferedIOViolation());
+            startHandlingViolationException(new UnbufferedIoViolation());
         }
 
         // Part of BlockGuard.Policy interface:
@@ -1409,7 +1508,7 @@
             //       go into this immediate mode?
             if (looper == null || (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
                 info.durationMillis = -1; // unknown (redundant, already set)
-                handleViolation(info);
+                onThreadPolicyViolation(info);
                 return;
             }
 
@@ -1447,30 +1546,28 @@
             THREAD_HANDLER
                     .get()
                     .postAtFrontOfQueue(
-                            new Runnable() {
-                                public void run() {
-                                    long loopFinishTime = SystemClock.uptimeMillis();
+                            () -> {
+                                long loopFinishTime = SystemClock.uptimeMillis();
 
-                                    // Note: we do this early, before handling the
-                                    // violation below, as handling the violation
-                                    // may include PENALTY_DEATH and we don't want
-                                    // to keep the red border on.
-                                    if (windowManager != null) {
-                                        try {
-                                            windowManager.showStrictModeViolation(false);
-                                        } catch (RemoteException unused) {
-                                        }
+                                // Note: we do this early, before handling the
+                                // violation below, as handling the violation
+                                // may include PENALTY_DEATH and we don't want
+                                // to keep the red border on.
+                                if (windowManager != null) {
+                                    try {
+                                        windowManager.showStrictModeViolation(false);
+                                    } catch (RemoteException unused) {
                                     }
-
-                                    for (int n = 0; n < records.size(); ++n) {
-                                        ViolationInfo v = records.get(n);
-                                        v.violationNumThisLoop = n + 1;
-                                        v.durationMillis =
-                                                (int) (loopFinishTime - v.violationUptimeMillis);
-                                        handleViolation(v);
-                                    }
-                                    records.clear();
                                 }
+
+                                for (int n = 0; n < records.size(); ++n) {
+                                    ViolationInfo v = records.get(n);
+                                    v.violationNumThisLoop = n + 1;
+                                    v.durationMillis =
+                                            (int) (loopFinishTime - v.violationUptimeMillis);
+                                    onThreadPolicyViolation(v);
+                                }
+                                records.clear();
                             });
         }
 
@@ -1479,13 +1576,13 @@
         // violation fired and now (after the violating code ran) due
         // to people who push/pop temporary policy in regions of code,
         // hence the policy being passed around.
-        void handleViolation(final ViolationInfo info) {
-            if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.mPolicy);
+        void onThreadPolicyViolation(final ViolationInfo info) {
+            if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; policy=" + info.mPolicy);
 
             if (info.penaltyEnabled(PENALTY_GATHER)) {
                 ArrayList<ViolationInfo> violations = gatheredViolations.get();
                 if (violations == null) {
-                    violations = new ArrayList<ViolationInfo>(1);
+                    violations = new ArrayList<>(1);
                     gatheredViolations.set(violations);
                 }
                 for (ViolationInfo previous : violations) {
@@ -1519,6 +1616,8 @@
                 sLogger.log(info);
             }
 
+            final Violation violation = info.mViolation;
+
             // The violationMaskSubset, passed to ActivityManager, is a
             // subset of the original StrictMode policy bitmask, with
             // only the bit violated and penalty bits to be executed
@@ -1552,15 +1651,32 @@
             }
 
             if ((info.getPolicyMask() & PENALTY_DEATH) != 0) {
-                executeDeathPenalty(info);
+                throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
+            }
+
+            // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
+            // executor finishes before crashing.
+            final OnThreadViolationListener listener = sThreadViolationListener.get();
+            final Executor executor = sThreadViolationExecutor.get();
+            if (listener != null && executor != null) {
+                try {
+                    executor.execute(
+                            () -> {
+                                // Lift violated policy to prevent infinite recursion.
+                                ThreadPolicy oldPolicy = allowThreadViolations();
+                                try {
+                                    listener.onThreadViolation(violation);
+                                } finally {
+                                    setThreadPolicy(oldPolicy);
+                                }
+                            });
+                } catch (RejectedExecutionException e) {
+                    Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
+                }
             }
         }
     }
 
-    private static void executeDeathPenalty(ViolationInfo info) {
-        throw new RuntimeException("StrictMode death penalty", info.mViolation);
-    }
-
     /**
      * In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
      * violations but not showing a dialog, not loggging, and not killing the process. In these
@@ -1736,9 +1852,8 @@
      * #setThreadPolicy}.
      */
     public static void enableDefaults() {
-        StrictMode.setThreadPolicy(
-                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
-        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+        setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
     }
 
     /** @hide */
@@ -1857,11 +1972,11 @@
     }
 
     /** @hide */
-    public static void onVmPolicyViolation(Violation originStack, boolean forceDeath) {
+    public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
         final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
         final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
         final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
-        final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
+        final ViolationInfo info = new ViolationInfo(violation, sVmPolicy.mask);
 
         // Erase stuff not relevant for process-wide violations
         info.numAnimationsRunning = 0;
@@ -1870,37 +1985,37 @@
 
         final Integer fingerprint = info.hashCode();
         final long now = SystemClock.uptimeMillis();
-        long lastViolationTime = 0;
+        long lastViolationTime;
         long timeSinceLastViolationMillis = Long.MAX_VALUE;
         synchronized (sLastVmViolationTime) {
             if (sLastVmViolationTime.containsKey(fingerprint)) {
                 lastViolationTime = sLastVmViolationTime.get(fingerprint);
                 timeSinceLastViolationMillis = now - lastViolationTime;
             }
-            if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+            if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
                 sLastVmViolationTime.put(fingerprint, now);
             }
         }
-
-        if (penaltyLog && sLogger != null) {
-            sLogger.log(info);
+        if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
+            // Rate limit all penalties.
+            return;
         }
-        if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+
+        if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
             sLogger.log(info);
         }
 
         int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
 
-        if (penaltyDropbox && !penaltyDeath) {
-            // Common case for userdebug/eng builds.  If no death and
-            // just dropboxing, we can do the ActivityManager call
-            // asynchronously.
-            dropboxViolationAsync(violationMaskSubset, info);
-            return;
-        }
-
-        if (penaltyDropbox && lastViolationTime == 0) {
-            handleApplicationStrictModeViolation(violationMaskSubset, info);
+        if (penaltyDropbox) {
+            if (penaltyDeath) {
+                handleApplicationStrictModeViolation(violationMaskSubset, info);
+            } else {
+                // Common case for userdebug/eng builds.  If no death and
+                // just dropboxing, we can do the ActivityManager call
+                // asynchronously.
+                dropboxViolationAsync(violationMaskSubset, info);
+            }
         }
 
         if (penaltyDeath) {
@@ -1908,6 +2023,26 @@
             Process.killProcess(Process.myPid());
             System.exit(10);
         }
+
+        // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
+        // all executors. penaltyDeath supersedes penaltyCallback.
+        if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
+            final OnVmViolationListener listener = sVmPolicy.mListener;
+            try {
+                sVmPolicy.mCallbackExecutor.execute(
+                        () -> {
+                            // Lift violated policy to prevent infinite recursion.
+                            VmPolicy oldPolicy = allowVmViolations();
+                            try {
+                                listener.onVmViolation(violation);
+                            } finally {
+                                setVmPolicy(oldPolicy);
+                            }
+                        });
+            } catch (RejectedExecutionException e) {
+                Log.e(TAG, "VmPolicy penaltyCallback failed", e);
+            }
+        }
     }
 
     /** Called from Parcel.writeNoException() */
@@ -2407,7 +2542,7 @@
                 return DETECT_CUSTOM;
             } else if (mViolation instanceof ResourceMismatchViolation) {
                 return DETECT_RESOURCE_MISMATCH;
-            } else if (mViolation instanceof UnbufferedIOViolation) {
+            } else if (mViolation instanceof UnbufferedIoViolation) {
                 return DETECT_UNBUFFERED_IO;
             } else if (mViolation instanceof SqliteObjectLeakedViolation) {
                 return DETECT_VM_CURSOR_LEAKS;
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index b3d76d7..c52c22d 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -16,12 +16,18 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.app.IAlarmManager;
 import android.content.Context;
 import android.util.Slog;
 
 import dalvik.annotation.optimization.CriticalNative;
 
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
 /**
  * Core timekeeping facilities.
  *
@@ -168,6 +174,31 @@
     native public static long uptimeMillis();
 
     /**
+     * Return {@link Clock} that starts at system boot, not counting time spent
+     * in deep sleep.
+     */
+    public static @NonNull Clock uptimeMillisClock() {
+        return new Clock() {
+            @Override
+            public ZoneId getZone() {
+                return ZoneOffset.UTC;
+            }
+            @Override
+            public Clock withZone(ZoneId zone) {
+                throw new UnsupportedOperationException();
+            }
+            @Override
+            public long millis() {
+                return SystemClock.uptimeMillis();
+            }
+            @Override
+            public Instant instant() {
+                return Instant.ofEpochMilli(millis());
+            }
+        };
+    }
+
+    /**
      * Returns milliseconds since boot, including time spent in sleep.
      *
      * @return elapsed milliseconds since boot.
@@ -176,6 +207,31 @@
     native public static long elapsedRealtime();
 
     /**
+     * Return {@link Clock} that starts at system boot, including time spent in
+     * sleep.
+     */
+    public static @NonNull Clock elapsedRealtimeClock() {
+        return new Clock() {
+            @Override
+            public ZoneId getZone() {
+                return ZoneOffset.UTC;
+            }
+            @Override
+            public Clock withZone(ZoneId zone) {
+                throw new UnsupportedOperationException();
+            }
+            @Override
+            public long millis() {
+                return SystemClock.elapsedRealtime();
+            }
+            @Override
+            public Instant instant() {
+                return Instant.ofEpochMilli(millis());
+            }
+        };
+    }
+
+    /**
      * Returns nanoseconds since boot, including time spent in sleep.
      *
      * @return elapsed nanoseconds since boot.
diff --git a/core/java/android/os/strictmode/CleartextNetworkViolation.java b/core/java/android/os/strictmode/CleartextNetworkViolation.java
index 893780d..6a0d381 100644
--- a/core/java/android/os/strictmode/CleartextNetworkViolation.java
+++ b/core/java/android/os/strictmode/CleartextNetworkViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class CleartextNetworkViolation extends Violation {
+    /** @hide */
     public CleartextNetworkViolation(String msg) {
         super(msg);
     }
diff --git a/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java
index 017c4f9..e78dc79 100644
--- a/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java
+++ b/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java
@@ -17,8 +17,8 @@
 
 import android.net.Uri;
 
-/** @hide */
 public final class ContentUriWithoutPermissionViolation extends Violation {
+    /** @hide */
     public ContentUriWithoutPermissionViolation(Uri uri, String location) {
         super(
                 uri
diff --git a/core/java/android/os/strictmode/CustomViolation.java b/core/java/android/os/strictmode/CustomViolation.java
index bc1656d..d4ad067 100644
--- a/core/java/android/os/strictmode/CustomViolation.java
+++ b/core/java/android/os/strictmode/CustomViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class CustomViolation extends Violation {
+    /** @hide */
     public CustomViolation(String name) {
         super(name);
     }
diff --git a/core/java/android/os/strictmode/DiskReadViolation.java b/core/java/android/os/strictmode/DiskReadViolation.java
index 2edd33e..fad32db 100644
--- a/core/java/android/os/strictmode/DiskReadViolation.java
+++ b/core/java/android/os/strictmode/DiskReadViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class DiskReadViolation extends Violation {
+    /** @hide */
     public DiskReadViolation() {
         super(null);
     }
diff --git a/core/java/android/os/strictmode/DiskWriteViolation.java b/core/java/android/os/strictmode/DiskWriteViolation.java
index 6465718..cb9ca38 100644
--- a/core/java/android/os/strictmode/DiskWriteViolation.java
+++ b/core/java/android/os/strictmode/DiskWriteViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class DiskWriteViolation extends Violation {
+    /** @hide */
     public DiskWriteViolation() {
         super(null);
     }
diff --git a/core/java/android/os/strictmode/FileUriExposedViolation.java b/core/java/android/os/strictmode/FileUriExposedViolation.java
index 5f71ee5..e3e6f83 100644
--- a/core/java/android/os/strictmode/FileUriExposedViolation.java
+++ b/core/java/android/os/strictmode/FileUriExposedViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class FileUriExposedViolation extends Violation {
+    /** @hide */
     public FileUriExposedViolation(String msg) {
         super(msg);
     }
diff --git a/core/java/android/os/strictmode/InstanceCountViolation.java b/core/java/android/os/strictmode/InstanceCountViolation.java
index d3da800..9ee2c8e 100644
--- a/core/java/android/os/strictmode/InstanceCountViolation.java
+++ b/core/java/android/os/strictmode/InstanceCountViolation.java
@@ -15,7 +15,6 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public class InstanceCountViolation extends Violation {
     private final long mInstances;
 
@@ -24,6 +23,7 @@
                 "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
     };
 
+    /** @hide */
     public InstanceCountViolation(Class klass, long instances, int limit) {
         super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
         setStackTrace(FAKE_STACK);
diff --git a/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java b/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java
index 1d1dfc0..f416c94 100644
--- a/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java
+++ b/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class IntentReceiverLeakedViolation extends Violation {
+    /** @hide */
     public IntentReceiverLeakedViolation(Throwable originStack) {
         super(null);
         setStackTrace(originStack.getStackTrace());
diff --git a/core/java/android/os/strictmode/LeakedClosableViolation.java b/core/java/android/os/strictmode/LeakedClosableViolation.java
index de12533..c795a6b 100644
--- a/core/java/android/os/strictmode/LeakedClosableViolation.java
+++ b/core/java/android/os/strictmode/LeakedClosableViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class LeakedClosableViolation extends Violation {
+    /** @hide */
     public LeakedClosableViolation(String message, Throwable allocationSite) {
         super(message);
         initCause(allocationSite);
diff --git a/core/java/android/os/strictmode/NetworkViolation.java b/core/java/android/os/strictmode/NetworkViolation.java
index 637d0b6..abcf009 100644
--- a/core/java/android/os/strictmode/NetworkViolation.java
+++ b/core/java/android/os/strictmode/NetworkViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class NetworkViolation extends Violation {
+    /** @hide */
     public NetworkViolation() {
         super(null);
     }
diff --git a/core/java/android/os/strictmode/ResourceMismatchViolation.java b/core/java/android/os/strictmode/ResourceMismatchViolation.java
index 01ae7f8..97c4499 100644
--- a/core/java/android/os/strictmode/ResourceMismatchViolation.java
+++ b/core/java/android/os/strictmode/ResourceMismatchViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class ResourceMismatchViolation extends Violation {
+    /** @hide */
     public ResourceMismatchViolation(Object tag) {
         super(tag.toString());
     }
diff --git a/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java b/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java
index 5bab3bd..2d6b58f 100644
--- a/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java
+++ b/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class ServiceConnectionLeakedViolation extends Violation {
+    /** @hide */
     public ServiceConnectionLeakedViolation(Throwable originStack) {
         super(null);
         setStackTrace(originStack.getStackTrace());
diff --git a/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java b/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java
index 8360088..0200220 100644
--- a/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java
+++ b/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java
@@ -15,9 +15,9 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class SqliteObjectLeakedViolation extends Violation {
 
+    /** @hide */
     public SqliteObjectLeakedViolation(String message, Throwable originStack) {
         super(message);
         initCause(originStack);
diff --git a/core/java/android/os/strictmode/UnbufferedIOViolation.java b/core/java/android/os/strictmode/UnbufferedIoViolation.java
similarity index 69%
rename from core/java/android/os/strictmode/UnbufferedIOViolation.java
rename to core/java/android/os/strictmode/UnbufferedIoViolation.java
index 571ba50..a5c326d 100644
--- a/core/java/android/os/strictmode/UnbufferedIOViolation.java
+++ b/core/java/android/os/strictmode/UnbufferedIoViolation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -15,9 +15,14 @@
  */
 package android.os.strictmode;
 
-/** @hide */
-public final class UnbufferedIOViolation extends Violation {
-    public UnbufferedIOViolation() {
+import android.os.StrictMode.ThreadPolicy.Builder;
+
+/**
+ * See #{@link Builder#detectUnbufferedIo()}
+ */
+public final class UnbufferedIoViolation extends Violation {
+    /** @hide */
+    public UnbufferedIoViolation() {
         super(null);
     }
 }
diff --git a/core/java/android/os/strictmode/UntaggedSocketViolation.java b/core/java/android/os/strictmode/UntaggedSocketViolation.java
index 9752753..836a8b9 100644
--- a/core/java/android/os/strictmode/UntaggedSocketViolation.java
+++ b/core/java/android/os/strictmode/UntaggedSocketViolation.java
@@ -15,13 +15,13 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class UntaggedSocketViolation extends Violation {
     /** @hide */
     public static final String MESSAGE =
             "Untagged socket detected; use"
                     + " TrafficStats.setThreadSocketTag() to track all network usage";
 
+    /** @hide */
     public UntaggedSocketViolation() {
         super(MESSAGE);
     }
diff --git a/core/java/android/os/strictmode/Violation.java b/core/java/android/os/strictmode/Violation.java
index ebae7fc..31c7d58 100644
--- a/core/java/android/os/strictmode/Violation.java
+++ b/core/java/android/os/strictmode/Violation.java
@@ -16,13 +16,9 @@
 
 package android.os.strictmode;
 
-/**
- * Root class for all StrictMode violations.
- *
- * @hide
- */
+/** Root class for all StrictMode violations. */
 public abstract class Violation extends Throwable {
-    protected Violation(String message) {
+    Violation(String message) {
         super(message);
     }
 }
diff --git a/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java b/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
index d4c557a..c328d14 100644
--- a/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
+++ b/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
@@ -15,8 +15,8 @@
  */
 package android.os.strictmode;
 
-/** @hide */
 public final class WebViewMethodCalledOnWrongThreadViolation extends Violation {
+    /** @hide */
     public WebViewMethodCalledOnWrongThreadViolation(Throwable originStack) {
         super(null);
         setStackTrace(originStack.getStackTrace());
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 13e1e26..32d68cd 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -81,6 +81,13 @@
     public static final String UNHIDE_CALL = "unhide";
 
     /**
+     * The method name used by the media scanner service to reload all localized ringtone titles due
+     * to a locale change.
+     * @hide
+     */
+    public static final String RETRANSLATE_CALL = "update_titles";
+
+    /**
      * This is for internal use by the media scanner only.
      * Name of the (optional) Uri parameter that determines whether to skip deleting
      * the file pointed to by the _data column, when deleting the database entry.
@@ -1358,6 +1365,18 @@
              * @hide
              */
             public static final String GENRE = "genre";
+
+            /**
+             * The resource URI of a localized title, if any
+             * <P>Type: TEXT</P>
+             * Conforms to this pattern:
+             *   Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
+             *   Authority: Package Name of ringtone title provider
+             *   First Path Segment: Type of resource (must be "string")
+             *   Second Path Segment: Resource ID of title
+             * @hide
+             */
+            public static final String TITLE_RESOURCE_URI = "title_resource_uri";
         }
 
         /**
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 3e992ec..080482b 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Service;
 import android.content.ComponentName;
@@ -56,6 +57,8 @@
  *           &lt;/meta-data>
  * &lt;/service></pre>
  *
+ *  <p> Condition providers cannot be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
  */
 public abstract class ConditionProviderService extends Service {
     private final String TAG = ConditionProviderService.class.getSimpleName()
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 08d3118..dac663e7 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -21,6 +21,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
@@ -82,6 +83,8 @@
  * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
  * or after {@link #onListenerDisconnected()}.
  * </p>
+ * <p> Notification listeners cannot get notification access or be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
  */
 public abstract class NotificationListenerService extends Service {
 
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 2b03ed6..cc4a0b6 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -340,6 +340,14 @@
     }
 
     /** @hide Just for debugging; not internationalized. */
+    public static String formatDuration(long duration) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, 0);
+            return new String(sFormatStr, 0, len);
+        }
+    }
+
+    /** @hide Just for debugging; not internationalized. */
     public static void formatDuration(long duration, PrintWriter pw) {
         formatDuration(duration, pw, 0);
     }
diff --git a/core/java/android/view/DisplayFrames.java b/core/java/android/view/DisplayFrames.java
new file mode 100644
index 0000000..e6861d8
--- /dev/null
+++ b/core/java/android/view/DisplayFrames.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 android.view;
+
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+
+/**
+ * Container class for all the display frames that affect how we do window layout on a display.
+ * @hide
+ */
+public class DisplayFrames {
+    public final int mDisplayId;
+
+    /**
+     * The current size of the screen; really; extends into the overscan area of the screen and
+     * doesn't account for any system elements like the status bar.
+     */
+    public final Rect mOverscan = new Rect();
+
+    /**
+     * The current visible size of the screen; really; (ir)regardless of whether the status bar can
+     * be hidden but not extending into the overscan area.
+     */
+    public final Rect mUnrestricted = new Rect();
+
+    /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
+    public final Rect mRestrictedOverscan = new Rect();
+
+    /**
+     * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
+     * can't be hidden; in that case it effectively carves out that area of the display from all
+     * other windows.
+     */
+    public final Rect mRestricted = new Rect();
+
+    /**
+     * During layout, the current screen borders accounting for any currently visible system UI
+     * elements.
+     */
+    public final Rect mSystem = new Rect();
+
+    /** For applications requesting stable content insets, these are them. */
+    public final Rect mStable = new Rect();
+
+    /**
+     * For applications requesting stable content insets but have also set the fullscreen window
+     * flag, these are the stable dimensions without the status bar.
+     */
+    public final Rect mStableFullscreen = new Rect();
+
+    /**
+     * During layout, the current screen borders with all outer decoration (status bar, input method
+     * dock) accounted for.
+     */
+    public final Rect mCurrent = new Rect();
+
+    /**
+     * During layout, the frame in which content should be displayed to the user, accounting for all
+     * screen decoration except for any space they deem as available for other content. This is
+     * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
+     * insets.
+     */
+    public final Rect mContent = new Rect();
+
+    /**
+     * During layout, the frame in which voice content should be displayed to the user, accounting
+     * for all screen decoration except for any space they deem as available for other content.
+     */
+    public final Rect mVoiceContent = new Rect();
+
+    /** During layout, the current screen borders along which input method windows are placed. */
+    public final Rect mDock = new Rect();
+
+    private final Rect mDisplayInfoOverscan = new Rect();
+    private final Rect mRotatedDisplayInfoOverscan = new Rect();
+    public int mDisplayWidth;
+    public int mDisplayHeight;
+
+    public int mRotation;
+
+    public DisplayFrames(int displayId, DisplayInfo info) {
+        mDisplayId = displayId;
+        onDisplayInfoUpdated(info);
+    }
+
+    public void onDisplayInfoUpdated(DisplayInfo info) {
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
+        mRotation = info.rotation;
+        mDisplayInfoOverscan.set(
+                info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
+    }
+
+    public void onBeginLayout() {
+        switch (mRotation) {
+            case ROTATION_90:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left;
+                break;
+            case ROTATION_180:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top;
+                break;
+            case ROTATION_270:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right;
+                break;
+            default:
+                mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
+                break;
+        }
+
+        mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
+        mOverscan.set(mRestrictedOverscan);
+        mSystem.set(mRestrictedOverscan);
+        mUnrestricted.set(mRotatedDisplayInfoOverscan);
+        mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
+        mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
+        mRestricted.set(mUnrestricted);
+        mDock.set(mUnrestricted);
+        mContent.set(mUnrestricted);
+        mVoiceContent.set(mUnrestricted);
+        mStable.set(mUnrestricted);
+        mStableFullscreen.set(mUnrestricted);
+        mCurrent.set(mUnrestricted);
+
+    }
+
+    public int getInputMethodWindowVisibleHeight() {
+        return mDock.bottom - mCurrent.bottom;
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mStable.writeToProto(proto, STABLE_BOUNDS);
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+                + " r=" + mRotation);
+        final String myPrefix = prefix + "  ";
+        dumpFrame(mStable, "mStable", myPrefix, pw);
+        dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw);
+        dumpFrame(mDock, "mDock", myPrefix, pw);
+        dumpFrame(mCurrent, "mCurrent", myPrefix, pw);
+        dumpFrame(mSystem, "mSystem", myPrefix, pw);
+        dumpFrame(mContent, "mContent", myPrefix, pw);
+        dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw);
+        dumpFrame(mOverscan, "mOverscan", myPrefix, pw);
+        dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw);
+        dumpFrame(mRestricted, "mRestricted", myPrefix, pw);
+        dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
+        dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw);
+        dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw);
+    }
+
+    private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
+        pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
+    }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 37829f0..e30496f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -513,7 +513,7 @@
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
         if (!sCompatibilityDone) {
-            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+            sAlwaysAssignFocus = true;
 
             sCompatibilityDone = true;
         }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c7e8dee..cca66d6 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -605,9 +605,10 @@
     public void setStoppedState(IBinder token, boolean stopped) {
         synchronized (mLock) {
             int count = mViews.size();
-            for (int i = 0; i < count; i++) {
+            for (int i = count - 1; i >= 0; i--) {
                 if (token == null || mParams.get(i).token == token) {
                     ViewRootImpl root = mRoots.get(i);
+                    // Client might remove the view by "stopped" event.
                     root.setWindowStopped(stopped);
                 }
             }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index ebe3633..534335b 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -66,7 +66,6 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
@@ -722,12 +721,6 @@
     public void setInitialDisplaySize(Display display, int width, int height, int density);
 
     /**
-     * Called by window manager to set the overscan region that should be used for the
-     * given display.
-     */
-    public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
-
-    /**
      * Check permissions when adding a window.
      *
      * @param attrs The window's LayoutParams.
@@ -1173,14 +1166,10 @@
     /**
      * Called when layout of the windows is about to start.
      *
-     * @param displayId Id of the display we are doing layout on.
-     * @param displayWidth The current full width of the screen.
-     * @param displayHeight The current full height of the screen.
-     * @param displayRotation The current rotation being applied to the base window.
+     * @param displayFrames frames of the display we are doing layout on.
      * @param uiMode The current uiMode in configuration.
      */
-    public void beginLayoutLw(int displayId, int displayWidth, int displayHeight,
-                              int displayRotation, int uiMode);
+    default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {}
 
     /**
      * Returns the bottom-most layer of the system decor, above which no policy decor should
@@ -1189,37 +1178,28 @@
     public int getSystemDecorLayerLw();
 
     /**
-     * Return the rectangle of the screen that is available for applications to run in.
-     * This will be called immediately after {@link #beginLayoutLw}.
-     *
-     * @param r The rectangle to be filled with the boundaries available to applications.
-     */
-    public void getContentRectLw(Rect r);
-
-    /**
-     * Called for each window attached to the window manager as layout is
-     * proceeding.  The implementation of this function must take care of
-     * setting the window's frame, either here or in finishLayout().
+     * Called for each window attached to the window manager as layout is proceeding. The
+     * implementation of this function must take care of setting the window's frame, either here or
+     * in finishLayout().
      *
      * @param win The window being positioned.
      * @param attached For sub-windows, the window it is attached to; this
      *                 window will already have had layoutWindow() called on it
      *                 so you can use its Rect.  Otherwise null.
+     * @param displayFrames The display frames.
      */
-    public void layoutWindowLw(WindowState win, WindowState attached);
+    default void layoutWindowLw(
+            WindowState win, WindowState attached, DisplayFrames displayFrames) {}
 
 
     /**
-     * Return the insets for the areas covered by system windows. These values
-     * are computed on the most recent layout, so they are not guaranteed to
-     * be correct.
+     * Return the insets for the areas covered by system windows. These values are computed on the
+     * most recent layout, so they are not guaranteed to be correct.
      *
      * @param attrs The LayoutParams of the window.
      * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
      *                   associated with the window.
-     * @param displayRotation Rotation of the display.
-     * @param displayWidth The width of the display.
-     * @param displayHeight The height of the display.
+     * @param displayFrames display frames.
      * @param outContentInsets The areas covered by system windows, expressed as positive insets.
      * @param outStableInsets The areas covered by stable system windows irrespective of their
      *                        current visibility. Expressed as positive insets.
@@ -1227,16 +1207,11 @@
      * @return Whether to always consume the navigation bar.
      *         See {@link #isNavBarForcedShownLw(WindowState)}.
      */
-    public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
-            Rect outStableInsets, Rect outOutsets);
-
-    /**
-     * Called when layout of the windows is finished.  After this function has
-     * returned, all windows given to layoutWindow() <em>must</em> have had a
-     * frame assigned.
-     */
-    public void finishLayoutLw();
+    default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+            DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets) {
+        return false;
+    }
 
     /** Layout state may have changed (so another layout will be performed) */
     static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
@@ -1653,11 +1628,6 @@
     public void showGlobalActions();
 
     /**
-     * @return The current height of the input method window.
-     */
-    public int getInputMethodWindowVisibleHeightLw();
-
-    /**
      * Called when the current user changes. Guaranteed to be called before the broadcast
      * of the new user id is made to all listeners.
      *
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2ec64a5..f2483c0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -3639,6 +3639,7 @@
 
     public void addIsolatedUidLocked(int isolatedUid, int appUid) {
         mIsolatedUids.put(isolatedUid, appUid);
+        StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
     }
 
     /**
@@ -3659,9 +3660,11 @@
      * @see #scheduleRemoveIsolatedUidLocked(int, int)
      */
     public void removeIsolatedUidLocked(int isolatedUid) {
-        mIsolatedUids.delete(isolatedUid);
-        mKernelUidCpuTimeReader.removeUid(isolatedUid);
-        mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+      StatsLog.write(
+          StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+      mIsolatedUids.delete(isolatedUid);
+      mKernelUidCpuTimeReader.removeUid(isolatedUid);
+      mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
     }
 
     public int mapUid(int uid) {
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index ac79249..18afe6e 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -137,18 +137,15 @@
 
 class StyleRun : public Run {
     public:
-        StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
-                std::shared_ptr<minikin::FontCollection>&& collection, bool isRtl)
-            : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
-              mIsRtl(isRtl) {}
+        StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint, bool isRtl)
+            : Run(start, end), mPaint(std::move(paint)), mIsRtl(isRtl) {}
 
         void addTo(minikin::LineBreaker* lineBreaker) override {
-            lineBreaker->addStyleRun(&mPaint, mCollection, mStart, mEnd, mIsRtl);
+            lineBreaker->addStyleRun(&mPaint, mStart, mEnd, mIsRtl);
         }
 
     private:
         minikin::MinikinPaint mPaint;
-        std::shared_ptr<minikin::FontCollection> mCollection;
         const bool mIsRtl;
 };
 
@@ -176,10 +173,8 @@
               mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
               mRightPaddings(std::move(rightPaddings)) {}
 
-        void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
-                         std::shared_ptr<minikin::FontCollection> collection, bool isRtl) {
-            mRuns.emplace_back(std::make_unique<StyleRun>(
-                    start, end, std::move(paint), std::move(collection), isRtl));
+        void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint, bool isRtl) {
+            mRuns.emplace_back(std::make_unique<StyleRun>(start, end, std::move(paint), isRtl));
         }
 
         void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
@@ -334,7 +329,7 @@
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
     const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
-    builder->addStyleRun(start, end, std::move(minikinPaint), typeface->fFontCollection, isRtl);
+    builder->addStyleRun(start, end, std::move(minikinPaint), isRtl);
 }
 
 // CriticalNative
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 4d48a42..0228edb 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -48,7 +48,6 @@
 
 /* represents PhoneWindowManager */
 message WindowManagerPolicyProto {
-  optional .android.graphics.RectProto stable_bounds = 1;
 }
 
 /* represents AppTransition */
@@ -101,8 +100,13 @@
   optional .android.view.DisplayInfoProto display_info = 10;
   optional int32 rotation = 11;
   optional ScreenRotationAnimationProto screen_rotation_animation = 12;
+  optional DisplayFramesProto display_frames = 13;
 }
 
+/* represents DisplayFrames */
+message DisplayFramesProto {
+  optional .android.graphics.RectProto stable_bounds = 1;
+}
 
 /* represents DockedStackDividerController */
 message DockedStackDividerControllerProto {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7cc43a7..1879ab7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -123,6 +123,9 @@
          be sent during a change to the audio output device. -->
     <bool name="config_sendAudioBecomingNoisy">true</bool>
 
+    <!-- Flag to disable all transition animations -->
+    <bool name="config_disableTransitionAnimation">false</bool>
+
     <!-- The duration (in milliseconds) of a short animation. -->
     <integer name="config_shortAnimTime">200</integer>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d630839..e476d89 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -252,6 +252,7 @@
   <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
   <java-symbol type="bool" name="config_cellBroadcastAppLinks" />
   <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
+  <java-symbol type="bool" name="config_disableTransitionAnimation" />
   <java-symbol type="bool" name="config_enableAutoPowerModes" />
   <java-symbol type="integer" name="config_autoPowerModeThresholdAngle" />
   <java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" />
diff --git a/core/tests/coretests/res/layout/add_column_in_table.xml b/core/tests/coretests/res/layout/add_column_in_table.xml
index 05f55a8..d929b02 100644
--- a/core/tests/coretests/res/layout/add_column_in_table.xml
+++ b/core/tests/coretests/res/layout/add_column_in_table.xml
@@ -18,6 +18,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <TableLayout android:id="@+id/table"
         android:layout_width="match_parent"
diff --git a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
index acbb10b..eac9b9d 100644
--- a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
+++ b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
@@ -21,6 +21,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
     <LinearLayout android:id="@+id/layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
diff --git a/core/tests/coretests/res/layout/focus_after_removal.xml b/core/tests/coretests/res/layout/focus_after_removal.xml
index f4e388d..84449d1 100644
--- a/core/tests/coretests/res/layout/focus_after_removal.xml
+++ b/core/tests/coretests/res/layout/focus_after_removal.xml
@@ -22,6 +22,7 @@
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
+    <requestFocus />
 
     <LinearLayout android:id="@+id/leftLayout"
         android:orientation="vertical"
diff --git a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
index ab76e29..6b3b5a7 100644
--- a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
+++ b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
@@ -22,6 +22,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <EditText
         android:id="@+id/editText"
diff --git a/core/tests/coretests/res/layout/visibility_callback.xml b/core/tests/coretests/res/layout/visibility_callback.xml
index 9034b3f..ff918f5 100644
--- a/core/tests/coretests/res/layout/visibility_callback.xml
+++ b/core/tests/coretests/res/layout/visibility_callback.xml
@@ -24,6 +24,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <requestFocus />
 
     <LinearLayout
       android:orientation="vertical"
diff --git a/core/tests/coretests/src/android/util/ListScenario.java b/core/tests/coretests/src/android/util/ListScenario.java
index fa088a3..129484a 100644
--- a/core/tests/coretests/src/android/util/ListScenario.java
+++ b/core/tests/coretests/src/android/util/ListScenario.java
@@ -28,6 +28,7 @@
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
+
 import com.google.android.collect.Maps;
 
 import java.util.ArrayList;
@@ -60,18 +61,18 @@
 
     // separators
     private Set<Integer> mUnselectableItems = new HashSet<Integer>();
-    
+
     private boolean mStackFromBottom;
 
     private int mClickedPosition = -1;
-    
+
     private int mLongClickedPosition = -1;
-    
+
     private int mConvertMisses = 0;
-    
+
     private int mHeaderViewCount;
     private boolean mHeadersFocusable;
-    
+
     private int mFooterViewCount;
     private LinearLayout mLinearLayout;
 
@@ -193,7 +194,7 @@
             mIncludeHeader = includeHeader;
             return this;
         }
-        
+
         /**
          * Sets the stacking direction
          * @param stackFromBottom
@@ -203,7 +204,7 @@
             mStackFromBottom = stackFromBottom;
             return this;
         }
-        
+
         /**
          * Sets whether the sum of the height of the list items must be at least the
          * height of the list view.
@@ -220,7 +221,7 @@
             mFadingEdgeScreenSizeFactor = fadingEdgeScreenSizeFactor;
             return this;
         }
-        
+
         /**
          * Set the number of header views to appear within the list
          */
@@ -246,7 +247,7 @@
             mFooterViewCount = footerViewCount;
             return this;
         }
-        
+
         /**
          * Sets whether the {@link ListScenario} will automatically set the
          * adapter on the list view. If this is false, the client MUST set it
@@ -278,7 +279,7 @@
      */
     protected void nothingSelected() {
     }
-    
+
     /**
      * Override this if you want to know when something has been clicked (perhaps
      * more importantly, that {@link android.widget.AdapterView.OnItemClickListener} has
@@ -287,7 +288,7 @@
     protected void positionClicked(int position) {
         setClickedPosition(position);
     }
-    
+
     /**
      * Override this if you want to know when something has been long clicked (perhaps
      * more importantly, that {@link android.widget.AdapterView.OnItemLongClickListener} has
@@ -303,7 +304,7 @@
 
         // for test stability, turn off title bar
         requestWindowFeature(Window.FEATURE_NO_TITLE);
-        
+
 
         mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
 
@@ -326,7 +327,7 @@
             header.setText("Header: " + i);
             mListView.addHeaderView(header);
         }
-        
+
         for (int i=0; i<mFooterViewCount; i++) {
             TextView header = new TextView(this);
             header.setText("Footer: " + i);
@@ -336,7 +337,7 @@
         if (params.mConnectAdapter) {
             setAdapter(mListView);
         }
-        
+
         mListView.setItemsCanFocus(mItemsFocusable);
         if (mStartingSelectionPosition >= 0) {
             mListView.setSelection(mStartingSelectionPosition);
@@ -360,11 +361,12 @@
                 positionClicked(position);
             }
         });
-        
+
         // set the fading edge length porportionally to the screen
         // height for test stability
         if (params.mFadingEdgeScreenSizeFactor != null) {
-            mListView.setFadingEdgeLength((int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));            
+            mListView.setFadingEdgeLength(
+                    (int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));
         } else {
             mListView.setFadingEdgeLength((int) ((64.0 / 480) * mScreenHeight));
         }
@@ -403,6 +405,7 @@
             mLinearLayout.addView(mListView);
             setContentView(mLinearLayout);
         }
+        mLinearLayout.restoreDefaultFocus();
     }
 
     /**
@@ -426,7 +429,7 @@
             }
         });
     }
-    
+
     /**
      * @return The newly created ListView widget.
      */
@@ -440,16 +443,16 @@
     protected Params createParams() {
         return new Params();
     }
-    
+
     /**
      * Sets an adapter on a ListView.
-     * 
+     *
      * @param listView The ListView to set the adapter on.
      */
     protected void setAdapter(ListView listView) {
         listView.setAdapter(new MyAdapter());
     }
-    
+
     /**
      * Read in and validate all of the params passed in by the scenario.
      * @param params
@@ -525,7 +528,7 @@
         if (!mIncludeHeader) {
             throw new IllegalArgumentException("no header above list");
         }
-        mHeaderTextView.setText(value);        
+        mHeaderTextView.setText(value);
     }
 
     /**
@@ -543,12 +546,12 @@
     }
 
     /**
-     * Convert a non-null view. 
+     * Convert a non-null view.
      */
     public View convertView(int position, View convertView, ViewGroup parent) {
         return ListItemFactory.convertText(convertView, getValueAtPosition(position), position);
     }
-    
+
     public void setClickedPosition(int clickedPosition) {
         mClickedPosition = clickedPosition;
     }
@@ -580,7 +583,7 @@
             }
         });
     }
-    
+
     /**
      * Return an item type for the specified position in the adapter. Override if your
      * adapter creates more than one type.
@@ -596,7 +599,7 @@
     public int getViewTypeCount() {
         return 1;
     }
-    
+
     /**
      * @return The number of times convertView failed
      */
@@ -647,7 +650,7 @@
             }
             return result;
         }
-        
+
         @Override
         public int getItemViewType(int position) {
             return ListScenario.this.getItemViewType(position);
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
index 1ba56ba..0d8d834 100644
--- a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
@@ -16,12 +16,12 @@
 
 package android.widget.layout.linear;
 
-import com.android.frameworks.coretests.R;
-
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
 
+import com.android.frameworks.coretests.R;
+
 public class LLOfTwoFocusableInTouchMode extends Activity {
 
     private View mButton1;
@@ -63,6 +63,7 @@
                 mB3Fired = true;
             }
         });
+        getWindow().getDecorView().restoreDefaultFocus();
     }
 
     public View getButton1() {
diff --git a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
index 98fbed3..9e49719 100644
--- a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
+++ b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
@@ -16,10 +16,9 @@
 
 package android.widget.listview;
 
-import android.util.InternalSelectionView;
-
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.InternalSelectionView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
@@ -80,6 +79,7 @@
 
 
         setContentView(combineAdjacent(mLeftListView, mRightListView));
+        getWindow().getDecorView().restoreDefaultFocus();
     }
 
     private static View combineAdjacent(View... views) {
diff --git a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
index 8f971bb..edc60b5 100644
--- a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
+++ b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
@@ -16,15 +16,12 @@
 
 package android.widget.listview.focus;
 
-import android.widget.listview.ListHorizontalFocusWithinItemWins;
-
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.view.FocusFinder;
 import android.view.KeyEvent;
-import android.view.View;
 import android.widget.Button;
 import android.widget.ListView;
+import android.widget.listview.ListHorizontalFocusWithinItemWins;
 
 public class ListHorizontalFocusWithinItemWinsTest extends ActivityInstrumentationTestCase<ListHorizontalFocusWithinItemWins> {
 
@@ -51,12 +48,6 @@
     public void testPreconditions() {
         assertEquals("list position", 0, mListView.getSelectedItemPosition());
         assertTrue("mTopLeftButton.isFocused()", mTopLeftButton.isFocused());
-        assertEquals("global focus search to right from top left is bottom middle",
-                mBottomMiddleButton,
-                FocusFinder.getInstance().findNextFocus(mListView, mTopLeftButton, View.FOCUS_RIGHT));
-        assertEquals("global focus search to left from top right is bottom middle",
-                mBottomMiddleButton,
-                FocusFinder.getInstance().findNextFocus(mListView, mTopRightButton, View.FOCUS_LEFT));
     }
 
     @MediumTest
diff --git a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
index bd6977e..5a6110c 100644
--- a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
+++ b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
@@ -16,7 +16,6 @@
 
 package android.widget.touchmode;
 
-import android.widget.layout.linear.LLOfButtons1;
 import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterClick;
 import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterTap;
 import static android.util.TouchModeFlexibleAsserts.assertNotInTouchModeAfterKey;
@@ -25,6 +24,8 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.KeyEvent;
 import android.widget.Button;
+import android.widget.layout.linear.LLOfButtons1;
+
 
 /**
  * Make sure focus isn't kept by buttons when entering touch mode.
@@ -52,7 +53,6 @@
     @MediumTest
     public void testPreconditions() {
         assertFalse("we should not be in touch mode", mActivity.isInTouchMode());
-        assertTrue("top button should have focus", mFirstButton.isFocused());
     }
 
     @MediumTest
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 3dc928d..aff942d 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -475,6 +475,19 @@
     }
 
     /**
+     * If the specified rectangle intersects this rectangle, set this rectangle to that
+     * intersection, otherwise set this rectangle to the empty rectangle.
+     * @see #inset(int, int, int, int) but without checking if the rects overlap.
+     * @hide
+     */
+    public void intersectUnchecked(Rect other) {
+        left = Math.max(left, other.left);
+        top = Math.max(top, other.top);
+        right = Math.min(right, other.right);
+        bottom = Math.min(bottom, other.bottom);
+    }
+
+    /**
      * If rectangles a and b intersect, return true and set this rectangle to
      * that intersection, otherwise return false and do not change this
      * rectangle. No check is performed to see if either rectangle is empty.
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 5484cf0..251b2e7 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -98,6 +98,9 @@
             enabled: true,
         },
     },
+    sanitize: {
+        blacklist: "libandroidfw_blacklist.txt",
+    },
 }
 
 common_test_libs = [
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5d248c2..3fe75cf 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -812,7 +812,13 @@
             *outLen = encLen;
 
             if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
-                return (const char*)str;
+                // Reject malformed (non null-terminated) strings
+                if (str[encLen] != 0x00) {
+                    ALOGW("Bad string block: string #%d is not null-terminated",
+                          (int)idx);
+                    return NULL;
+                }
+              return (const char*)str;
             } else {
                 ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
                         (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
diff --git a/libs/androidfw/libandroidfw_blacklist.txt b/libs/androidfw/libandroidfw_blacklist.txt
new file mode 100644
index 0000000..dd17e4d
--- /dev/null
+++ b/libs/androidfw/libandroidfw_blacklist.txt
@@ -0,0 +1 @@
+src:*/ResourceTypes.cpp
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d90caf9..d41db63 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -178,7 +178,7 @@
 }
 
 RenderPipelineType Properties::getRenderPipelineType() {
-    if (RenderPipelineType::NotInitialized != sRenderPipelineType) {
+    if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
         return sRenderPipelineType;
     }
     char prop[PROPERTY_VALUE_MAX];
@@ -196,11 +196,17 @@
     return sRenderPipelineType;
 }
 
-#ifdef HWUI_GLES_WRAP_ENABLED
 void Properties::overrideRenderPipelineType(RenderPipelineType type) {
+#if !defined(HWUI_GLES_WRAP_ENABLED)
+    // If we're doing actual rendering then we can't change the renderer after it's been set.
+    // Unit tests can freely change this as often as it wants, though, as there's no actual
+    // GL rendering happening
+    if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
+        return;
+    }
+#endif
     sRenderPipelineType = type;
 }
-#endif
 
 bool Properties::isSkiaEnabled() {
     auto renderType = getRenderPipelineType();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index fbd9b7a..9c30e4a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -255,10 +255,8 @@
 
     static bool skpCaptureEnabled;
 
-// Used for testing only to change the render pipeline.
-#ifdef HWUI_GLES_WRAP_ENABLED
+    // Used for testing only to change the render pipeline.
     static void overrideRenderPipelineType(RenderPipelineType);
-#endif
 
 private:
     static ProfileType sProfileType;
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 90fe0b7..2a1a1f3 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -30,46 +30,43 @@
                                                         const Typeface* typeface) {
     const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
     minikin::FontStyle resolved = resolvedFace->fStyle;
-
     const minikin::FontVariant minikinVariant =
             (paint->getFontVariant() == minikin::FontVariant::ELEGANT)
                     ? minikin::FontVariant::ELEGANT
                     : minikin::FontVariant::COMPACT;
-
-    minikin::MinikinPaint minikinPaint;
-    /* Prepare minikin Paint */
-    minikinPaint.size =
-            paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize());
-    minikinPaint.scaleX = paint->getTextScaleX();
-    minikinPaint.skewX = paint->getTextSkewX();
-    minikinPaint.letterSpacing = paint->getLetterSpacing();
-    minikinPaint.wordSpacing = paint->getWordSpacing();
-    minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
-    minikinPaint.localeListId = paint->getMinikinLocaleListId();
-    minikinPaint.fontStyle = minikin::FontStyle(minikinVariant, resolved.weight, resolved.slant);
-    minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
-    minikinPaint.hyphenEdit = minikin::HyphenEdit(paint->getHyphenEdit());
-    return minikinPaint;
+    float textSize = paint->getTextSize();
+    if (!paint->isLinearText()) {
+        // If linear text is not specified, truncate the value.
+        textSize = trunc(textSize);
+    }
+    return minikin::MinikinPaint(
+            textSize,
+            paint->getTextScaleX(),
+            paint->getTextSkewX(),
+            paint->getLetterSpacing(),
+            paint->getWordSpacing(),
+            MinikinFontSkia::packPaintFlags(paint),
+            paint->getMinikinLocaleListId(),
+            minikin::FontStyle(minikinVariant, resolved.weight, resolved.slant),
+            minikin::HyphenEdit(paint->getHyphenEdit()),
+            paint->getFontFeatureSettings(),
+            resolvedFace->fFontCollection);
 }
 
 minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags,
                                        const Typeface* typeface, const uint16_t* buf, size_t start,
                                        size_t count, size_t bufSize) {
-    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
     minikin::Layout layout;
-    layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint,
-                    Typeface::resolveDefault(typeface)->fFontCollection);
+    layout.doLayout(buf, start, count, bufSize, bidiFlags, prepareMinikinPaint(paint, typeface));
     return layout;
 }
 
 float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                 const Typeface* typeface, const uint16_t* buf, size_t start,
                                 size_t count, size_t bufSize, float* advances) {
-    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
-    const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
-    return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint,
-                                        resolvedTypeface->fFontCollection, advances,
-                                        nullptr /* extent */, nullptr /* overhangs */);
+    return minikin::Layout::measureText(
+            buf, start, count, bufSize, bidiFlags, prepareMinikinPaint(paint, typeface), advances,
+            nullptr /* extent */, nullptr /* overhangs */);
 }
 
 bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6657fe8..91b35c2 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -144,22 +144,25 @@
 // ----------------------------------------------------------------------------
 
 inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint,
-                                         sk_sp<SkColorFilter> colorFilter) {
-    if ((origPaint && origPaint->isAntiAlias()) || colorFilter) {
+                                         sk_sp<SkColorFilter> colorSpaceFilter) {
+    if ((origPaint && origPaint->isAntiAlias()) || colorSpaceFilter) {
         if (origPaint) {
             *tmpPaint = *origPaint;
         }
 
-        sk_sp<SkColorFilter> filter;
-        if (colorFilter && tmpPaint->getColorFilter()) {
-            filter = SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorFilter);
-            LOG_ALWAYS_FATAL_IF(!filter);
-        } else {
-            filter = colorFilter;
+        if (colorSpaceFilter) {
+            if (tmpPaint->getColorFilter()) {
+                tmpPaint->setColorFilter(
+                        SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter));
+            } else {
+                tmpPaint->setColorFilter(colorSpaceFilter);
+            }
+            LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
         }
 
+
+        // disabling AA on bitmap draws matches legacy HWUI behavior
         tmpPaint->setAntiAlias(false);
-        tmpPaint->setColorFilter(filter);
         return tmpPaint;
     } else {
         return origPaint;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 1d8cdd6..a693e68 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -602,6 +602,7 @@
         mDeviceWaitIdle(mBackendContext->fDevice);
     }
 
+    SkASSERT(surface->mBackbuffers);
     VulkanSurface::BackbufferInfo* backbuffer =
             surface->mBackbuffers + surface->mCurrentBackbufferIndex;
     GrVkImageInfo* imageInfo;
@@ -683,6 +684,7 @@
 }
 
 int VulkanManager::getAge(VulkanSurface* surface) {
+    SkASSERT(surface->mBackbuffers);
     VulkanSurface::BackbufferInfo* backbuffer =
             surface->mBackbuffers + surface->mCurrentBackbufferIndex;
     if (mSwapBehavior == SwapBehavior::Discard ||
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b9898a7..c319c9e 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -59,11 +59,11 @@
     VkSurfaceKHR mVkSurface = VK_NULL_HANDLE;
     VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
 
-    BackbufferInfo* mBackbuffers;
+    BackbufferInfo* mBackbuffers = nullptr;
     uint32_t mCurrentBackbufferIndex;
 
     uint32_t mImageCount;
-    VkImage* mImages;
+    VkImage* mImages = nullptr;
     ImageInfo* mImageInfos;
     uint16_t mCurrentTime = 0;
 };
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f1ff939..8c0ca5c 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -68,6 +68,7 @@
   --onscreen           Render tests on device screen. By default tests
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
+  --renderer=TYPE      Sets the render pipeline to use. May be opengl, skiagl, or skiavk
 )");
 }
 
@@ -146,6 +147,20 @@
     return true;
 }
 
+static bool setRenderer(const char* renderer) {
+    if (!strcmp(renderer, "opengl")) {
+        Properties::overrideRenderPipelineType(RenderPipelineType::OpenGL);
+    } else if (!strcmp(renderer, "skiagl")) {
+        Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+    } else if (!strcmp(renderer, "skiavk")) {
+        Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
+    } else {
+        fprintf(stderr, "Unknown format '%s'", renderer);
+        return false;
+    }
+    return true;
+}
+
 // For options that only exist in long-form. Anything in the
 // 0-255 range is reserved for short options (which just use their ASCII value)
 namespace LongOpts {
@@ -158,6 +173,7 @@
     BenchmarkFormat,
     Onscreen,
     Offscreen,
+    Renderer,
 };
 }
 
@@ -172,6 +188,7 @@
         {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
+        {"renderer", required_argument, nullptr, LongOpts::Renderer},
         {0, 0, 0, 0}};
 
 static const char* SHORT_OPTIONS = "c:r:h";
@@ -252,6 +269,16 @@
                 }
                 break;
 
+            case LongOpts::Renderer:
+                if (!optarg) {
+                    error = true;
+                    break;
+                }
+                if (!setRenderer(optarg)) {
+                    error = true;
+                }
+                break;
+
             case LongOpts::Onscreen:
                 gOpts.renderOffscreen = false;
                 break;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 20405d3..7afe267 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -244,6 +244,7 @@
         SUPPRESSIBLE_USAGES.put(USAGE_GAME,                              SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
         SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN,                           SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
     }
 
     /**
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index dab7632a..58976ca 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -43,17 +43,16 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.Pair;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -1966,9 +1965,28 @@
      */
     private boolean querySoundEffectsEnabled(int user) {
         return Settings.System.getIntForUser(getContext().getContentResolver(),
-                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
+                Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
+                && !areSystemSoundsZenModeBlocked(getContext());
     }
 
+    private boolean areSystemSoundsZenModeBlocked(Context context) {
+        int zenMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.ZEN_MODE, 0);
+
+        switch (zenMode) {
+            case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+            case Settings.Global.ZEN_MODE_ALARMS:
+                return true;
+            case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                final NotificationManager noMan = (NotificationManager) context
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
+                return (noMan.getNotificationPolicy().priorityCategories
+                        & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+            case Settings.Global.ZEN_MODE_OFF:
+            default:
+                return false;
+        }
+    }
 
     /**
      *  Load Sound effects.
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index fa50943..478297f 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -80,6 +80,9 @@
     // The minimum allowed max packet size is 255 according to the OBEX specification
     public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
 
+    // The length of OBEX Byte Sequency Header Id according to the OBEX specification
+    public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
+
     /**
      * Temporary workaround to be able to push files to Windows 7.
      * TODO: Should be removed as soon as Microsoft updates their driver.
@@ -205,12 +208,15 @@
                     case 0x40:
                         boolean trimTail = true;
                         index++;
-                        length = 0xFF & headerArray[index];
-                        length = length << 8;
-                        index++;
-                        length += 0xFF & headerArray[index];
-                        length -= 3;
-                        index++;
+                        length = ((0xFF & headerArray[index]) << 8) +
+                                 (0xFF & headerArray[index + 1]);
+                        index += 2;
+                        if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
+                            Log.e(TAG, "Remote sent an OBEX packet with " +
+                                  "incorrect header length = " + length);
+                            break;
+                        }
+                        length -= OBEX_BYTE_SEQ_HEADER_LEN;
                         value = new byte[length];
                         System.arraycopy(headerArray, index, value, 0, length);
                         if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 386c4e0..88f0d2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -20,14 +20,16 @@
      * Displays preference in this controller.
      */
     public void displayPreference(PreferenceScreen screen) {
+        final String prefKey = getPreferenceKey();
         if (isAvailable()) {
+            setVisible(screen, prefKey, true /* visible */);
             if (this instanceof Preference.OnPreferenceChangeListener) {
-                final Preference preference = screen.findPreference(getPreferenceKey());
+                final Preference preference = screen.findPreference(prefKey);
                 preference.setOnPreferenceChangeListener(
                         (Preference.OnPreferenceChangeListener) this);
             }
         } else {
-            removePreference(screen, getPreferenceKey());
+            setVisible(screen, prefKey, false /* visible */);
         }
     }
 
@@ -59,13 +61,6 @@
     public abstract String getPreferenceKey();
 
     /**
-     * Removes preference from screen.
-     */
-    protected final void removePreference(PreferenceScreen screen, String key) {
-        findAndRemovePreference(screen, key);
-    }
-
-    /**
      * Show/hide a preference.
      */
     protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
@@ -74,25 +69,4 @@
             pref.setVisible(isVisible);
         }
     }
-
-    // finds the preference recursively and removes it from its parent
-    private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
-        final int preferenceCount = prefGroup.getPreferenceCount();
-        for (int i = 0; i < preferenceCount; i++) {
-            final Preference preference = prefGroup.getPreference(i);
-            final String curKey = preference.getKey();
-
-            if (curKey != null && curKey.equals(key)) {
-                return prefGroup.removePreference(preference);
-            }
-
-            if (preference instanceof PreferenceGroup) {
-                if (findAndRemovePreference((PreferenceGroup) preference, key)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
index ff7536a..90f14ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -18,18 +18,20 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.core.AbstractPreferenceController;
 
 /**
  * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
  */
 public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
-    private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+    @VisibleForTesting
+    static final String KEY_SERIAL_NUMBER = "serial_number";
 
     private final String mSerialNumber;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
index c08bc2a..26970e1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
@@ -16,17 +16,10 @@
 package com.android.settingslib.core;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceGroup;
-import android.support.v7.preference.PreferenceManager;
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settingslib.TestConfig;
@@ -39,134 +32,58 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowApplication;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class AbstractPreferenceControllerTest {
 
     @Mock
-    private Context mContext;
-    @Mock
     private PreferenceScreen mScreen;
 
+    private Context mContext;
     private Preference mPreference;
     private TestPrefController mTestPrefController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mPreference = new Preference(RuntimeEnvironment.application);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
         mPreference.setKey(TestPrefController.KEY_PREF);
+        when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
         mTestPrefController = new TestPrefController(mContext);
     }
 
     @Test
-    public void removeExistingPref_shouldBeRemoved() {
-        when(mScreen.getPreferenceCount()).thenReturn(1);
-        when(mScreen.getPreference(0)).thenReturn(mPreference);
-
-        mTestPrefController.removePreference(mScreen, TestPrefController.KEY_PREF);
-
-        verify(mScreen).removePreference(mPreference);
-    }
-
-    @Test
-    public void removeNonExistingPref_shouldNotRemoveAnything() {
-        mTestPrefController.removePreference(mScreen, TestPrefController.KEY_PREF);
-
-        verify(mScreen, never()).removePreference(any(Preference.class));
-    }
-
-    @Test
     public void displayPref_ifAvailable() {
         mTestPrefController.isAvailable = true;
 
         mTestPrefController.displayPreference(mScreen);
 
-        verify(mScreen, never()).removePreference(any(Preference.class));
+        assertThat(mPreference.isVisible()).isTrue();
     }
 
     @Test
     public void setVisible_prefIsVisible_shouldSetToVisible() {
-        when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
-
         mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, true /* visible */);
+
         assertThat(mPreference.isVisible()).isTrue();
     }
 
     @Test
     public void setVisible_prefNotVisible_shouldSetToInvisible() {
-        when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
-
         mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, false /* visible */);
+
         assertThat(mPreference.isVisible()).isFalse();
     }
 
     @Test
     public void doNotDisplayPref_ifNotAvailable() {
-        when(mScreen.getPreferenceCount()).thenReturn(1);
-        when(mScreen.getPreference(0)).thenReturn(mPreference);
         mTestPrefController.isAvailable = false;
 
         mTestPrefController.displayPreference(mScreen);
 
-        verify(mScreen).removePreference(any(Preference.class));
-    }
-
-    @Test
-    public void removePreference_shouldRemoveRecursively() {
-        final Context context = ShadowApplication.getInstance().getApplicationContext();
-        final PreferenceManager preferenceManager = mock(PreferenceManager.class);
-        // Top level
-        PreferenceScreen prefRoot = spy(new PreferenceScreen(context, null));
-        when(prefRoot.getPreferenceManager()).thenReturn(preferenceManager);
-        Preference pref1 = mock(Preference.class);
-        when(pref1.getKey()).thenReturn("key1");
-        PreferenceGroup prefGroup2 = spy(new PreferenceScreen(context, null));
-        when(prefGroup2.getPreferenceManager()).thenReturn(preferenceManager);
-        when(prefGroup2.getKey()).thenReturn("group2");
-        Preference pref3 = mock(Preference.class);
-        when(pref3.getKey()).thenReturn("key3");
-        PreferenceGroup prefGroup4 = spy(new PreferenceScreen(context, null));
-        when(prefGroup4.getPreferenceManager()).thenReturn(preferenceManager);
-        when(prefGroup4.getKey()).thenReturn("group4");
-        prefRoot.addPreference(pref1);
-        prefRoot.addPreference(prefGroup2);
-        prefRoot.addPreference(pref3);
-        prefRoot.addPreference(prefGroup4);
-
-        // 2nd level
-        Preference pref21 = mock(Preference.class);
-        when(pref21.getKey()).thenReturn("key21");
-        Preference pref22 = mock(Preference.class);
-        when(pref22.getKey()).thenReturn("key22");
-        prefGroup2.addPreference(pref21);
-        prefGroup2.addPreference(pref22);
-        PreferenceGroup prefGroup41 = spy(new PreferenceScreen(context, null));
-        when(prefGroup41.getKey()).thenReturn("group41");
-        when(prefGroup41.getPreferenceManager()).thenReturn(preferenceManager);
-        Preference pref42 = mock(Preference.class);
-        when(pref42.getKey()).thenReturn("key42");
-        prefGroup4.addPreference(prefGroup41);
-        prefGroup4.addPreference(pref42);
-
-        // 3rd level
-        Preference pref411 = mock(Preference.class);
-        when(pref411.getKey()).thenReturn("key411");
-        Preference pref412 = mock(Preference.class);
-        when(pref412.getKey()).thenReturn("key412");
-        prefGroup41.addPreference(pref411);
-        prefGroup41.addPreference(pref412);
-
-        mTestPrefController.removePreference(prefRoot, "key1");
-        verify(prefRoot).removePreference(pref1);
-
-        mTestPrefController.removePreference(prefRoot, "key411");
-        verify(prefGroup41).removePreference(pref411);
-
-        mTestPrefController.removePreference(prefRoot, "group41");
-        verify(prefGroup4).removePreference(prefGroup41);
+        assertThat(mPreference.isVisible()).isFalse();
     }
 
     private class TestPrefController extends AbstractPreferenceController {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index 32fa01c..aac736a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -17,14 +17,9 @@
 package com.android.settingslib.development;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Answers.RETURNS_DEEP_STUBS;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -67,21 +62,19 @@
         shadowContext.setSystemService(Context.USER_SERVICE, mUserManager);
         mContext = spy(shadowContext.getApplicationContext());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        mPreference = new SwitchPreference(mContext);
-        when(mScreen.findPreference(anyString())).thenReturn(mPreference);
         mController = new ConcreteEnableAdbPreferenceController(mContext);
+        mPreference = new SwitchPreference(mContext);
         mPreference.setKey(mController.getPreferenceKey());
+        when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
     }
 
     @Test
     public void displayPreference_isNotAdmin_shouldRemovePreference() {
         when(mUserManager.isAdminUser()).thenReturn(false);
-        when(mScreen.getPreferenceCount()).thenReturn(1);
-        when(mScreen.getPreference(0)).thenReturn(mPreference);
 
         mController.displayPreference(mScreen);
 
-        verify(mScreen).removePreference(any(Preference.class));
+        assertThat(mPreference.isVisible()).isFalse();
     }
 
     @Test
@@ -90,7 +83,7 @@
 
         mController.displayPreference(mScreen);
 
-        verify(mScreen, never()).removePreference(any(Preference.class));
+        assertThat(mPreference.isVisible()).isTrue();
     }
 
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
index 4dbb957..34bbf4f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
@@ -17,13 +17,6 @@
 package com.android.settingslib.deviceinfo;
 
 import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Answers.RETURNS_DEEP_STUBS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -38,68 +31,67 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 @RunWith(SettingsLibRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class SerialNumberPreferenceControllerTest {
 
-    @Mock(answer = RETURNS_DEEP_STUBS)
-    private Context mContext;
-    @Mock(answer = RETURNS_DEEP_STUBS)
+    @Mock
     private PreferenceScreen mScreen;
 
+    private Context mContext;
+    private Preference mPreference;
     private AbstractSerialNumberPreferenceController mController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPreference = new Preference(mContext);
+        mPreference.setKey(AbstractSerialNumberPreferenceController.KEY_SERIAL_NUMBER);
+        when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
     }
 
     @Test
     public void testIsAvaiable_noSerial_shouldReturnFalse() {
-        mController = new ConcreteSerialNumberPreferenceController(mContext, null);
+        mController = new TestPreferenceController(mContext, null);
 
         assertThat(mController.isAvailable()).isFalse();
     }
 
     @Test
-    public void testIsAvaiable_hasSerial_shouldReturnTrue() {
-        mController = new ConcreteSerialNumberPreferenceController(mContext, "123");
+    public void testIsAvailable_hasSerial_shouldReturnTrue() {
+        mController = new TestPreferenceController(mContext, "123");
 
         assertThat(mController.isAvailable()).isTrue();
     }
 
     @Test
     public void testDisplay_noSerial_shouldHidePreference() {
-        final Preference preference = mock(Preference.class);
-        when(mScreen.getPreferenceCount()).thenReturn(1);
-        when(mScreen.getPreference(0)).thenReturn(preference);
-        mController = new ConcreteSerialNumberPreferenceController(mContext, null);
-        when(preference.getKey()).thenReturn(mController.getPreferenceKey());
+        mController = new TestPreferenceController(mContext, null);
 
         mController.displayPreference(mScreen);
 
-        verify(mScreen).removePreference(any(Preference.class));
+        assertThat(mPreference.isVisible()).isFalse();
     }
 
     @Test
     public void testDisplay_hasSerial_shouldSetSummary() {
         final String serial = "123";
-        final Preference preference = mock(Preference.class);
-        when(mScreen.findPreference(anyString())).thenReturn(preference);
 
-        mController = new ConcreteSerialNumberPreferenceController(mContext, serial);
+        mController = new TestPreferenceController(mContext, serial);
         mController.displayPreference(mScreen);
 
-        verify(mScreen, never()).removePreference(any(Preference.class));
-        verify(preference).setSummary(serial);
+        assertThat(mPreference.isVisible()).isTrue();
+        assertThat(mPreference.getSummary()).isEqualTo(serial);
     }
 
-    private static class ConcreteSerialNumberPreferenceController
+    private static class TestPreferenceController
             extends AbstractSerialNumberPreferenceController {
 
-        ConcreteSerialNumberPreferenceController(Context context, String serialNumber) {
+        TestPreferenceController(Context context, String serialNumber) {
             super(context, serialNumber);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 9d960a1..039e13c 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -25,6 +25,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
@@ -32,22 +33,27 @@
 
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Class to send information from overview to launcher with a binder.
  */
-public class OverviewProxyService {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener> {
 
     private static final String TAG = "OverviewProxyService";
     private static final long BACKOFF_MILLIS = 5000;
 
     private final Context mContext;
     private final Handler mHandler;
-    private final Runnable mConnectionRunnable = this::startConnectionToCurrentUser;
+    private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
     private final DeviceProvisionedController mDeviceProvisionedController
             = Dependency.get(DeviceProvisionedController.class);
+    private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
@@ -82,6 +88,7 @@
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to call onBind()", e);
                 }
+                notifyConnectionChanged();
             }
         }
 
@@ -96,14 +103,14 @@
             @Override
             public void onUserSetupChanged() {
                 if (mDeviceProvisionedController.isCurrentUserSetup()) {
-                    startConnectionToCurrentUser();
+                    internalConnectToCurrentUser();
                 }
             }
 
             @Override
             public void onUserSwitched() {
                 mConnectionBackoffAttempts = 0;
-                startConnectionToCurrentUser();
+                internalConnectToCurrentUser();
             }
         };
 
@@ -111,7 +118,7 @@
     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt = new IBinder.DeathRecipient() {
         @Override
         public void binderDied() {
-            mOverviewProxy = null;
+            startConnectionToCurrentUser();
         }
     };
 
@@ -123,6 +130,14 @@
     }
 
     public void startConnectionToCurrentUser() {
+        if (mHandler.getLooper() != Looper.myLooper()) {
+            mHandler.post(mConnectionRunnable);
+        } else {
+            internalConnectToCurrentUser();
+        }
+    }
+
+    private void internalConnectToCurrentUser() {
         disconnectFromLauncherService();
 
         // If user has not setup yet or already connected, do not try to connect
@@ -144,14 +159,37 @@
         }
     }
 
+    @Override
+    public void addCallback(OverviewProxyListener listener) {
+        mConnectionCallbacks.add(listener);
+        listener.onConnectionChanged(mOverviewProxy != null);
+    }
+
+    @Override
+    public void removeCallback(OverviewProxyListener listener) {
+        mConnectionCallbacks.remove(listener);
+    }
+
     public IOverviewProxy getProxy() {
         return mOverviewProxy;
     }
 
     private void disconnectFromLauncherService() {
         if (mOverviewProxy != null) {
+            mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
             mContext.unbindService(mOverviewServiceConnection);
             mOverviewProxy = null;
+            notifyConnectionChanged();
         }
     }
+
+    private void notifyConnectionChanged() {
+        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+            mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
+        }
+    }
+
+    public interface OverviewProxyListener {
+        void onConnectionChanged(boolean isConnected);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 28adca9..a35ba9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -28,6 +28,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
@@ -1691,6 +1692,8 @@
             mUiOffloadThread.submit(() -> {
                 // If the stream is muted, don't play the sound
                 if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+                // If DND blocks the sound, don't play the sound
+                if (areSystemSoundsZenModeBlocked(mContext)) return;
 
                 int id = mLockSounds.play(soundId,
                         mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1702,6 +1705,25 @@
         }
     }
 
+    private boolean areSystemSoundsZenModeBlocked(Context context) {
+        int zenMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.ZEN_MODE, 0);
+
+        switch (zenMode) {
+            case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+            case Settings.Global.ZEN_MODE_ALARMS:
+                return true;
+            case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                final NotificationManager noMan = (NotificationManager) context
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
+                return (noMan.getNotificationPolicy().priorityCategories
+                        & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+            case Settings.Global.ZEN_MODE_OFF:
+            default:
+                return false;
+        }
+    }
+
     private void playTrustedSound() {
         playSound(mTrustedSoundId);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index a436e17..5eb315e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -208,6 +208,8 @@
      *
      * @param isHomeStackVisible if provided, will return whether the home stack is visible
      *                           regardless of the recents visibility
+     *
+     * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
      */
     public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
         if (mIam == null) return false;
@@ -222,13 +224,13 @@
                 final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
                 final int activityType = winConfig.getActivityType();
                 final int windowingMode = winConfig.getWindowingMode();
-                if (activityType == ACTIVITY_TYPE_HOME) {
+                if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
                     homeStackInfo = stackInfo;
-                } else if (activityType == ACTIVITY_TYPE_STANDARD
+                } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
                         && (windowingMode == WINDOWING_MODE_FULLSCREEN
                             || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                     fullscreenStackInfo = stackInfo;
-                } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+                } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
                     recentsStackInfo = stackInfo;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 54d622b..c4024a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -18,46 +18,21 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.app.INotificationManager;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.Switch;
-import android.widget.TextView;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
-import java.util.Set;
-
 /**
  * The guts of a notification revealed when performing a long press.
  */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
new file mode 100644
index 0000000..b585bdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -0,0 +1,355 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
+ * closing guts, and keeping track of the currently exposed notification guts.
+ */
+public class NotificationGutsManager implements Dumpable {
+    private static final String TAG = "NotificationGutsManager";
+
+    // Must match constant in Settings. Used to highlight preferences when linking to Settings.
+    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final Set<String> mNonBlockablePkgs;
+    private final NotificationPresenter mPresenter;
+    // TODO: Create NotificationListContainer interface and use it instead of
+    // NotificationStackScrollLayout here
+    private final NotificationStackScrollLayout mStackScroller;
+    private final Context mContext;
+    private final AccessibilityManager mAccessibilityManager;
+    // which notification is currently being longpress-examined by the user
+    private NotificationGuts mNotificationGutsExposed;
+    private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
+    private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private String mKeyToRemoveOnGutsClosed;
+
+    public NotificationGutsManager(
+            NotificationPresenter presenter,
+            NotificationStackScrollLayout stackScroller,
+            NotificationInfo.CheckSaveListener checkSaveListener,
+            Context context) {
+        mPresenter = presenter;
+        mStackScroller = stackScroller;
+        mCheckSaveListener = checkSaveListener;
+        mContext = context;
+        Resources res = context.getResources();
+
+        mNonBlockablePkgs = new HashSet<>();
+        Collections.addAll(mNonBlockablePkgs, res.getStringArray(
+                com.android.internal.R.array.config_nonBlockableNotificationPackages));
+
+        mAccessibilityManager = (AccessibilityManager)
+                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+    }
+
+    public String getKeyToRemoveOnGutsClosed() {
+        return mKeyToRemoveOnGutsClosed;
+    }
+
+    public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
+        mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
+    }
+
+    private void saveAndCloseNotificationMenu(
+            ExpandableNotificationRow row, NotificationGuts guts, View done) {
+        guts.resetFalsingCheck();
+        int[] rowLocation = new int[2];
+        int[] doneLocation = new int[2];
+        row.getLocationOnScreen(rowLocation);
+        done.getLocationOnScreen(doneLocation);
+
+        final int centerX = done.getWidth() / 2;
+        final int centerY = done.getHeight() / 2;
+        final int x = doneLocation[0] - rowLocation[0] + centerX;
+        final int y = doneLocation[1] - rowLocation[1] + centerY;
+        closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+                true /* removeControls */, x, y, true /* resetMenu */);
+    }
+
+    /**
+     * Sends an intent to open the notification settings for a particular package and optional
+     * channel.
+     */
+    private void startAppNotificationSettingsActivity(String packageName, final int appUid,
+            final NotificationChannel channel) {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+        if (channel != null) {
+            intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+        }
+        mPresenter.startNotificationGutsIntent(intent, appUid);
+    }
+
+    public void bindGuts(final ExpandableNotificationRow row) {
+        bindGuts(row, mGutsMenuItem);
+    }
+
+    private void bindGuts(final ExpandableNotificationRow row,
+            NotificationMenuRowPlugin.MenuItem item) {
+        row.inflateGuts();
+        row.setGutsView(item);
+        final StatusBarNotification sbn = row.getStatusBarNotification();
+        row.setTag(sbn.getPackageName());
+        final NotificationGuts guts = row.getGuts();
+        guts.setClosedListener((NotificationGuts g) -> {
+            if (!g.willBeRemoved() && !row.isRemoved()) {
+                mStackScroller.onHeightChanged(
+                        row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
+            }
+            if (mNotificationGutsExposed == g) {
+                mNotificationGutsExposed = null;
+                mGutsMenuItem = null;
+            }
+            String key = sbn.getKey();
+            if (key.equals(mKeyToRemoveOnGutsClosed)) {
+                mKeyToRemoveOnGutsClosed = null;
+                mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+            }
+        });
+
+        View gutsView = item.getGutsView();
+        if (gutsView instanceof NotificationSnooze) {
+            NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setStatusBarNotification(sbn);
+            snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
+            guts.setHeightChangedListener((NotificationGuts g) -> {
+                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+            });
+        }
+
+        if (gutsView instanceof NotificationInfo) {
+            final UserHandle userHandle = sbn.getUser();
+            PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                    userHandle.getIdentifier());
+            final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            final String pkg = sbn.getPackageName();
+            NotificationInfo info = (NotificationInfo) gutsView;
+            // Settings link is only valid for notifications that specify a user, unless this is the
+            // system user.
+            NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+            if (!userHandle.equals(UserHandle.ALL)
+                    || mPresenter.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+                onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+                    guts.resetFalsingCheck();
+                    startAppNotificationSettingsActivity(pkg, appUid, channel);
+                };
+            }
+            final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
+                    Intent intent) -> {
+                mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+                guts.resetFalsingCheck();
+                mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
+            };
+            final View.OnClickListener onDoneClick = (View v) -> {
+                saveAndCloseNotificationMenu(row, guts, v);
+            };
+
+            ArraySet<NotificationChannel> channels = new ArraySet<>();
+            channels.add(row.getEntry().channel);
+            if (row.isSummaryWithChildren()) {
+                // If this is a summary, then add in the children notification channels for the
+                // same user and pkg.
+                final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+                final int numChildren = childrenRows.size();
+                for (int i = 0; i < numChildren; i++) {
+                    final ExpandableNotificationRow childRow = childrenRows.get(i);
+                    final NotificationChannel childChannel = childRow.getEntry().channel;
+                    final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+                    if (childSbn.getUser().equals(userHandle) &&
+                            childSbn.getPackageName().equals(pkg)) {
+                        channels.add(childChannel);
+                    }
+                }
+            }
+            try {
+                info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
+                        row.getEntry().channel.getImportance(), sbn, onSettingsClick,
+                        onAppSettingsClick, onDoneClick, mCheckSaveListener,
+                        mNonBlockablePkgs);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    /**
+     * Closes guts or notification menus that might be visible and saves any changes.
+     *
+     * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
+     * @param force true if guts should be closed regardless of state (used for snooze only).
+     * @param removeControls true if controls (e.g. info) should be closed.
+     * @param x if closed based on touch location, this is the x touch location.
+     * @param y if closed based on touch location, this is the y touch location.
+     * @param resetMenu if any notification menus that might be revealed should be closed.
+     */
+    public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
+            int x, int y, boolean resetMenu) {
+        if (mNotificationGutsExposed != null) {
+            mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
+        }
+        if (resetMenu) {
+            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+        }
+    }
+
+    /**
+     * Returns the exposed NotificationGuts or null if none are exposed.
+     */
+    public NotificationGuts getExposedGuts() {
+        return mNotificationGutsExposed;
+    }
+
+    public void setExposedGuts(NotificationGuts guts) {
+        mNotificationGutsExposed = guts;
+    }
+
+    /**
+     *  Opens guts on the given ExpandableNotificationRow |v|.
+     *
+     * @param v ExpandableNotificationRow to open guts on
+     * @param x x coordinate of origin of circular reveal
+     * @param y y coordinate of origin of circular reveal
+     * @param item MenuItem the guts should display
+     * @return true if guts was opened
+     */
+    public boolean openGuts(View v, int x, int y,
+            NotificationMenuRowPlugin.MenuItem item) {
+        if (!(v instanceof ExpandableNotificationRow)) {
+            return false;
+        }
+
+        if (v.getWindowToken() == null) {
+            Log.e(TAG, "Trying to show notification guts, but not attached to window");
+            return false;
+        }
+
+        final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+        if (row.isDark()) {
+            return false;
+        }
+        v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        if (row.areGutsExposed()) {
+            closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+                    true /* removeControls */, -1 /* x */, -1 /* y */,
+                    true /* resetMenu */);
+            return false;
+        }
+        bindGuts(row, item);
+        NotificationGuts guts = row.getGuts();
+
+        // Assume we are a status_bar_notification_row
+        if (guts == null) {
+            // This view has no guts. Examples are the more card or the dismiss all view
+            return false;
+        }
+
+        mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+
+        // ensure that it's laid but not visible until actually laid out
+        guts.setVisibility(View.INVISIBLE);
+        // Post to ensure the the guts are properly laid out.
+        guts.post(new Runnable() {
+            @Override
+            public void run() {
+                if (row.getWindowToken() == null) {
+                    Log.e(TAG, "Trying to show notification guts, but not attached to "
+                            + "window");
+                    return;
+                }
+                closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+                        true /* removeControls */, -1 /* x */, -1 /* y */,
+                        false /* resetMenu */);
+                guts.setVisibility(View.VISIBLE);
+                final double horz = Math.max(guts.getWidth() - x, x);
+                final double vert = Math.max(guts.getHeight() - y, y);
+                final float r = (float) Math.hypot(horz, vert);
+                final Animator a
+                        = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+                a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+                a.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        // Move the notification view back over the menu
+                        row.resetTranslation();
+                    }
+                });
+                a.start();
+                final boolean needsFalsingProtection =
+                        (mPresenter.isPresenterLocked() &&
+                                !mAccessibilityManager.isTouchExplorationEnabled());
+                guts.setExposed(true /* exposed */, needsFalsingProtection);
+                row.closeRemoteInput();
+                mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                mNotificationGutsExposed = guts;
+                mGutsMenuItem = item;
+            }
+        });
+        return true;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("mKeyToRemoveOnGutsClosed: ");
+        pw.println(mKeyToRemoveOnGutsClosed);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 3b23a0c..8d1bb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -84,7 +84,7 @@
     public interface CheckSaveListener {
         // Invoked when importance has changed and the NotificationInfo wants to try to save it.
         // Listener should run saveImportance unless the change should be canceled.
-        void checkSave(Runnable saveImportance);
+        void checkSave(Runnable saveImportance, StatusBarNotification sbn);
     }
 
     public interface OnSettingsClickListener {
@@ -409,7 +409,7 @@
     public boolean handleCloseControls(boolean save, boolean force) {
         if (save && hasImportanceChanged()) {
             if (mCheckSaveListener != null) {
-                mCheckSaveListener.checkSave(() -> { saveImportance(); });
+                mCheckSaveListener.checkSave(this::saveImportance, mSbn);
             } else {
                 saveImportance();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
new file mode 100644
index 0000000..e65bab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -0,0 +1,245 @@
+package com.android.systemui.statusbar;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles tasks and state related to media notifications. For example, there is a 'current' media
+ * notification, which this class keeps track of.
+ */
+public class NotificationMediaManager implements Dumpable {
+    private static final String TAG = "NotificationMediaManager";
+    public static final boolean DEBUG_MEDIA = false;
+
+    private final NotificationPresenter mPresenter;
+    private final Context mContext;
+    private final MediaSessionManager mMediaSessionManager;
+    private MediaController mMediaController;
+    private String mMediaNotificationKey;
+    private MediaMetadata mMediaMetadata;
+
+    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            super.onPlaybackStateChanged(state);
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
+            }
+            if (state != null) {
+                if (!isPlaybackActive(state.getState())) {
+                    clearCurrentMediaNotification();
+                    mPresenter.updateMediaMetaData(true, true);
+                }
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            super.onMetadataChanged(metadata);
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
+            }
+            mMediaMetadata = metadata;
+            mPresenter.updateMediaMetaData(true, true);
+        }
+    };
+
+    public NotificationMediaManager(NotificationPresenter presenter, Context context) {
+        mPresenter = presenter;
+        mContext = context;
+        mMediaSessionManager
+                = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
+        // in session state
+    }
+
+    public void onNotificationRemoved(String key) {
+        if (key.equals(mMediaNotificationKey)) {
+            clearCurrentMediaNotification();
+            mPresenter.updateMediaMetaData(true, true);
+        }
+    }
+
+    public String getMediaNotificationKey() {
+        return mMediaNotificationKey;
+    }
+
+    public MediaMetadata getMediaMetadata() {
+        return mMediaMetadata;
+    }
+
+    public void findAndUpdateMediaNotifications() {
+        boolean metaDataChanged = false;
+
+        synchronized (mPresenter.getNotificationData()) {
+            ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+                    .getNotificationData().getActiveNotifications();
+            final int N = activeNotifications.size();
+
+            // Promote the media notification with a controller in 'playing' state, if any.
+            NotificationData.Entry mediaNotification = null;
+            MediaController controller = null;
+            for (int i = 0; i < N; i++) {
+                final NotificationData.Entry entry = activeNotifications.get(i);
+
+                if (isMediaNotification(entry)) {
+                    final MediaSession.Token token =
+                            entry.notification.getNotification().extras.getParcelable(
+                                    Notification.EXTRA_MEDIA_SESSION);
+                    if (token != null) {
+                        MediaController aController = new MediaController(mContext, token);
+                        if (PlaybackState.STATE_PLAYING ==
+                                getMediaControllerPlaybackState(aController)) {
+                            if (DEBUG_MEDIA) {
+                                Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+                                        + entry.notification.getKey());
+                            }
+                            mediaNotification = entry;
+                            controller = aController;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (mediaNotification == null) {
+                // Still nothing? OK, let's just look for live media sessions and see if they match
+                // one of our notifications. This will catch apps that aren't (yet!) using media
+                // notifications.
+
+                if (mMediaSessionManager != null) {
+                    // TODO: Should this really be for all users?
+                    final List<MediaController> sessions
+                            = mMediaSessionManager.getActiveSessionsForUser(
+                            null,
+                            UserHandle.USER_ALL);
+
+                    for (MediaController aController : sessions) {
+                        if (PlaybackState.STATE_PLAYING ==
+                                getMediaControllerPlaybackState(aController)) {
+                            // now to see if we have one like this
+                            final String pkg = aController.getPackageName();
+
+                            for (int i = 0; i < N; i++) {
+                                final NotificationData.Entry entry = activeNotifications.get(i);
+                                if (entry.notification.getPackageName().equals(pkg)) {
+                                    if (DEBUG_MEDIA) {
+                                        Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+                                                + entry.notification.getKey());
+                                    }
+                                    controller = aController;
+                                    mediaNotification = entry;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (controller != null && !sameSessions(mMediaController, controller)) {
+                // We have a new media session
+                clearCurrentMediaNotification();
+                mMediaController = controller;
+                mMediaController.registerCallback(mMediaListener);
+                mMediaMetadata = mMediaController.getMetadata();
+                if (DEBUG_MEDIA) {
+                    Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
+                            + mMediaMetadata);
+                }
+
+                if (mediaNotification != null) {
+                    mMediaNotificationKey = mediaNotification.notification.getKey();
+                    if (DEBUG_MEDIA) {
+                        Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+                                + mMediaNotificationKey + " controller=" + mMediaController);
+                    }
+                }
+                metaDataChanged = true;
+            }
+        }
+
+        if (metaDataChanged) {
+            mPresenter.updateNotifications();
+        }
+        mPresenter.updateMediaMetaData(metaDataChanged, true);
+    }
+
+    public void clearCurrentMediaNotification() {
+        mMediaNotificationKey = null;
+        mMediaMetadata = null;
+        if (mMediaController != null) {
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+                        + mMediaController.getPackageName());
+            }
+            mMediaController.unregisterCallback(mMediaListener);
+        }
+        mMediaController = null;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("    mMediaSessionManager=");
+        pw.println(mMediaSessionManager);
+        pw.print("    mMediaNotificationKey=");
+        pw.println(mMediaNotificationKey);
+        pw.print("    mMediaController=");
+        pw.print(mMediaController);
+        if (mMediaController != null) {
+            pw.print(" state=" + mMediaController.getPlaybackState());
+        }
+        pw.println();
+        pw.print("    mMediaMetadata=");
+        pw.print(mMediaMetadata);
+        if (mMediaMetadata != null) {
+            pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+        }
+        pw.println();
+    }
+
+    private boolean isPlaybackActive(int state) {
+        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+                && state != PlaybackState.STATE_NONE;
+    }
+
+    private boolean sameSessions(MediaController a, MediaController b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null) {
+            return false;
+        }
+        return a.controlsSameSession(b);
+    }
+
+    private int getMediaControllerPlaybackState(MediaController controller) {
+        if (controller != null) {
+            final PlaybackState playbackState = controller.getPlaybackState();
+            if (playbackState != null) {
+                return playbackState.getState();
+            }
+        }
+        return PlaybackState.STATE_NONE;
+    }
+
+    private boolean isMediaNotification(NotificationData.Entry entry) {
+        // TODO: confirm that there's a valid media key
+        return entry.getExpandedContentView() != null &&
+                entry.getExpandedContentView()
+                        .findViewById(com.android.internal.R.id.media_actions) != null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
new file mode 100644
index 0000000..1aca60c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.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 com.android.systemui.statusbar;
+
+import android.content.Intent;
+import android.service.notification.NotificationListenerService;
+
+/**
+ * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
+ * for both querying the state of the system (some modularised piece of functionality may
+ * want to act differently based on e.g. whether the presenter is visible to the user or not) and
+ * for affecting the state of the system (e.g. starting an intent, given that the presenter may
+ * want to perform some action before doing so).
+ */
+public interface NotificationPresenter {
+
+    /**
+     * Returns true if the presenter is not visible. For example, it may not be necessary to do
+     * animations if this returns true.
+     */
+    boolean isPresenterFullyCollapsed();
+
+    /**
+     * Returns true if the presenter is locked. For example, if the keyguard is active.
+     */
+    boolean isPresenterLocked();
+
+    /**
+     * Returns the current user id. This can change if the user is switched.
+     */
+    int getCurrentUserId();
+
+    /**
+     * Runs the given intent. The presenter may want to run some animations or close itself when
+     * this happens.
+     */
+    void startNotificationGutsIntent(Intent intent, int appUid);
+
+    /**
+     * Returns NotificationData.
+     */
+    NotificationData getNotificationData();
+
+    // TODO: Create NotificationEntryManager and move this method to there.
+    /**
+     * Signals that some notifications have changed, and NotificationPresenter should update itself.
+     */
+    void updateNotifications();
+
+    /**
+     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+     */
+    void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
+
+    // TODO: Create NotificationUpdateHandler and move this method to there.
+    /**
+     * Removes a notification.
+     */
+    void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+    // TODO: Create NotificationEntryManager and move this method to there.
+    /**
+     * Gets the latest ranking map.
+     */
+    NotificationListenerService.RankingMap getLatestRankingMap();
+}
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 4c2a2f5..fc420eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -383,6 +383,12 @@
         }
     }
 
+    @Override
+    public void prepareFadeIn() {
+        super.prepareFadeIn();
+        setVisible(true /* visible */, false /* force */);
+    }
+
     private void resetTransformedView(View child) {
         TransformState ownState = TransformState.createFrom(child, mTransformInfo);
         ownState.resetTransformedView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 717699b..4180884 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -112,20 +112,26 @@
         mIsRTL = isRTL;
     }
 
-    public boolean onInterceptTouchEvent(MotionEvent event) {
+    private boolean proxyMotionEvents(MotionEvent event) {
         final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
         if (overviewProxy != null) {
             mNavigationBarView.requestUnbufferedDispatch(event);
             try {
                 overviewProxy.onMotionEvent(event);
+                return true;
             } catch (RemoteException e) {
                 Log.e(TAG, "Callback failed", e);
             }
         }
+        return false;
+    }
 
-        // If we move more than a fixed amount, then start capturing for the
-        // task switcher detector
-        mTaskSwitcherDetector.onTouchEvent(event);
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (!proxyMotionEvents(event)) {
+            // If we move more than a fixed amount, then start capturing for the
+            // task switcher detector, disabled when proxying motion events to launcher service
+            mTaskSwitcherDetector.onTouchEvent(event);
+        }
         int action = event.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
@@ -289,7 +295,7 @@
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+        boolean result = proxyMotionEvents(event) || mTaskSwitcherDetector.onTouchEvent(event);
         if (mDockWindowEnabled) {
             result |= handleDockWindowEvent(event);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 094129c..4e7f205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -47,6 +47,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.DockedStackExistsListener;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.PluginListener;
@@ -196,6 +198,9 @@
         }
     }
 
+    private final OverviewProxyListener mOverviewProxyListener =
+            isConnected -> setSlippery(!isConnected);
+
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -531,6 +536,24 @@
         }
     }
 
+    private void setSlippery(boolean slippery) {
+        boolean changed = false;
+        final ViewGroup navbarView = ((ViewGroup) getParent());
+        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
+                .getLayoutParams();
+        if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
+            lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
+            changed = true;
+        } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
+            lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
+            changed = true;
+        }
+        if (changed) {
+            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+            wm.updateViewLayout(navbarView, lp);
+        }
+    }
+
     public void setMenuVisibility(final boolean show) {
         setMenuVisibility(show, false);
     }
@@ -756,6 +779,7 @@
         onPluginDisconnected(null); // Create default gesture helper
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
+        Dependency.get(OverviewProxyService.class).addCallback(mOverviewProxyListener);
     }
 
     @Override
@@ -765,6 +789,7 @@
         if (mGestureHelper != null) {
             mGestureHelper.destroy();
         }
+        Dependency.get(OverviewProxyService.class).removeCallback(mOverviewProxyListener);
     }
 
     @Override
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 af03440..86a8f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -596,7 +596,7 @@
             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
         }
         closeQs();
-        mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
+        mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
                 true /* cancelAnimators */);
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 46e3aa1..e3ed581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -20,11 +20,12 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
-
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
 import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -40,10 +41,8 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.INotificationManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
-import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
@@ -80,10 +79,7 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -115,14 +111,12 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.Display;
-import android.view.HapticFeedbackConstants;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
-import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.ViewTreeObserver;
@@ -181,7 +175,6 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
@@ -207,10 +200,11 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationGutsManager;
 import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
@@ -243,7 +237,6 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
         .OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.volume.VolumeComponent;
@@ -255,10 +248,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Stack;
 
 public class StatusBar extends SystemUI implements DemoMode,
@@ -267,7 +258,7 @@
         ActivatableNotificationView.OnActivatedListener,
         ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
         ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
-        ColorExtractor.OnColorsChangedListener, ConfigurationListener {
+        ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
     public static final boolean MULTIUSER_DEBUG = false;
 
     public static final boolean ENABLE_REMOTE_INPUT =
@@ -287,9 +278,6 @@
     protected static final boolean ENABLE_HEADS_UP = true;
     protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
-    // Must match constant in Settings. Used to highlight preferences when linking to Settings.
-    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
     // Should match the values in PhoneWindowManager
@@ -308,7 +296,6 @@
     public static final boolean SPEW = false;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
     public static final boolean DEBUG_GESTURES = false;
-    public static final boolean DEBUG_MEDIA = false;
     public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
     public static final boolean DEBUG_CAMERA_LIFT = false;
 
@@ -458,6 +445,8 @@
     private final int[] mAbsPos = new int[2];
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
+    private NotificationGutsManager mGutsManager;
+
     // for disabling the status bar
     private int mDisabled1 = 0;
     private int mDisabled2 = 0;
@@ -553,31 +542,7 @@
     protected final PorterDuffXfermode mSrcOverXferMode =
             new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
-    private MediaSessionManager mMediaSessionManager;
-    private MediaController mMediaController;
-    private String mMediaNotificationKey;
-    private MediaMetadata mMediaMetadata;
-    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            super.onPlaybackStateChanged(state);
-            if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
-            if (state != null) {
-                if (!isPlaybackActive(state.getState())) {
-                    clearCurrentMediaNotification();
-                    updateMediaMetaData(true, true);
-                }
-            }
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadata metadata) {
-            super.onMetadataChanged(metadata);
-            if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
-            mMediaMetadata = metadata;
-            updateMediaMetaData(true, true);
-        }
-    };
+    private NotificationMediaManager mMediaManager;
 
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
@@ -700,7 +665,6 @@
     private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
-    private String mKeyToRemoveOnGutsClosed;
     private SysuiColorExtractor mColorExtractor;
     private ForegroundServiceController mForegroundServiceController;
     private ScreenLifecycle mScreenLifecycle;
@@ -817,6 +781,8 @@
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
 
+        mMediaManager = new NotificationMediaManager(this, mContext);
+
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
         mCommandQueue.addCallbacks(this);
@@ -903,16 +869,8 @@
             Slog.e(TAG, "Failed to register VR mode state listener: " + e);
         }
 
-        mNonBlockablePkgs = new HashSet<>();
-        Collections.addAll(mNonBlockablePkgs, res.getStringArray(
-                com.android.internal.R.array.config_nonBlockableNotificationPackages));
         // end old BaseStatusBar.start().
 
-        mMediaSessionManager
-                = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-        // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
-        // in session state
-
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
         mSettingsObserver.onChange(false); // set up
@@ -960,6 +918,8 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+        mGutsManager = new NotificationGutsManager(this, mStackScroller,
+                mCheckSaveListener, mContext);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1302,12 +1262,12 @@
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         for (int i = 0; i < activeNotifications.size(); i++) {
             Entry entry = activeNotifications.get(i);
-            boolean exposedGuts = mNotificationGutsExposed != null
-                    && entry.row.getGuts() == mNotificationGutsExposed;
+            boolean exposedGuts = mGutsManager.getExposedGuts() != null
+                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
             entry.row.onDensityOrFontScaleChanged();
             if (exposedGuts) {
-                mNotificationGutsExposed = entry.row.getGuts();
-                bindGuts(entry.row, mGutsMenuItem);
+                mGutsManager.setExposedGuts(entry.row.getGuts());
+                mGutsManager.bindGuts(entry.row);
             }
         }
     }
@@ -1672,6 +1632,7 @@
         updateNotifications();
     }
 
+    @Override
     public void removeNotification(String key, RankingMap ranking) {
         boolean deferRemoval = false;
         abortExistingInflation(key);
@@ -1685,10 +1646,8 @@
                     || !mVisualStabilityManager.isReorderingAllowed();
             deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
         }
-        if (key.equals(mMediaNotificationKey)) {
-            clearCurrentMediaNotification();
-            updateMediaMetaData(true, true);
-        }
+        mMediaManager.onNotificationRemoved(key);
+
         Entry entry = mNotificationData.get(key);
         if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
                 && entry.row != null && !entry.row.isDismissed()) {
@@ -1744,12 +1703,12 @@
             mRemoteInputEntriesToRemoveOnCollapse.add(entry);
             return;
         }
-        if (entry != null && mNotificationGutsExposed != null
-                && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
-                && !entry.row.getGuts().isLeavebehind()) {
+        if (entry != null && mGutsManager.getExposedGuts() != null
+                && mGutsManager.getExposedGuts() == entry.row.getGuts()
+                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
             Log.w(TAG, "Keeping notification because it's showing guts. " + key);
             mLatestRankingMap = ranking;
-            mKeyToRemoveOnGutsClosed = key;
+            mGutsManager.setKeyToRemoveOnGutsClosed(key);
             return;
         }
 
@@ -2142,7 +2101,8 @@
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
 
-    protected void updateNotifications() {
+    @Override
+    public void updateNotifications() {
         mNotificationData.filterAndSort();
 
         updateNotificationShade();
@@ -2184,136 +2144,9 @@
             }
         }
 
-        findAndUpdateMediaNotifications();
+        mMediaManager.findAndUpdateMediaNotifications();
     }
 
-    public void findAndUpdateMediaNotifications() {
-        boolean metaDataChanged = false;
-
-        synchronized (mNotificationData) {
-            ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-            final int N = activeNotifications.size();
-
-            // Promote the media notification with a controller in 'playing' state, if any.
-            Entry mediaNotification = null;
-            MediaController controller = null;
-            for (int i = 0; i < N; i++) {
-                final Entry entry = activeNotifications.get(i);
-
-                if (isMediaNotification(entry)) {
-                    final MediaSession.Token token =
-                            entry.notification.getNotification().extras.getParcelable(
-                                    Notification.EXTRA_MEDIA_SESSION);
-                    if (token != null) {
-                        MediaController aController = new MediaController(mContext, token);
-                        if (PlaybackState.STATE_PLAYING ==
-                                getMediaControllerPlaybackState(aController)) {
-                            if (DEBUG_MEDIA) {
-                                Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
-                                        + entry.notification.getKey());
-                            }
-                            mediaNotification = entry;
-                            controller = aController;
-                            break;
-                        }
-                    }
-                }
-            }
-            if (mediaNotification == null) {
-                // Still nothing? OK, let's just look for live media sessions and see if they match
-                // one of our notifications. This will catch apps that aren't (yet!) using media
-                // notifications.
-
-                if (mMediaSessionManager != null) {
-                    final List<MediaController> sessions
-                            = mMediaSessionManager.getActiveSessionsForUser(
-                                    null,
-                                    UserHandle.USER_ALL);
-
-                    for (MediaController aController : sessions) {
-                        if (PlaybackState.STATE_PLAYING ==
-                                getMediaControllerPlaybackState(aController)) {
-                            // now to see if we have one like this
-                            final String pkg = aController.getPackageName();
-
-                            for (int i = 0; i < N; i++) {
-                                final Entry entry = activeNotifications.get(i);
-                                if (entry.notification.getPackageName().equals(pkg)) {
-                                    if (DEBUG_MEDIA) {
-                                        Log.v(TAG, "DEBUG_MEDIA: found controller matching "
-                                                + entry.notification.getKey());
-                                    }
-                                    controller = aController;
-                                    mediaNotification = entry;
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (controller != null && !sameSessions(mMediaController, controller)) {
-                // We have a new media session
-                clearCurrentMediaNotification();
-                mMediaController = controller;
-                mMediaController.registerCallback(mMediaListener);
-                mMediaMetadata = mMediaController.getMetadata();
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
-                            + mMediaMetadata);
-                }
-
-                if (mediaNotification != null) {
-                    mMediaNotificationKey = mediaNotification.notification.getKey();
-                    if (DEBUG_MEDIA) {
-                        Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
-                                + mMediaNotificationKey + " controller=" + mMediaController);
-                    }
-                }
-                metaDataChanged = true;
-            }
-        }
-
-        if (metaDataChanged) {
-            updateNotifications();
-        }
-        updateMediaMetaData(metaDataChanged, true);
-    }
-
-    private int getMediaControllerPlaybackState(MediaController controller) {
-        if (controller != null) {
-            final PlaybackState playbackState = controller.getPlaybackState();
-            if (playbackState != null) {
-                return playbackState.getState();
-            }
-        }
-        return PlaybackState.STATE_NONE;
-    }
-
-    private boolean isPlaybackActive(int state) {
-        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
-                && state != PlaybackState.STATE_NONE;
-    }
-
-    private void clearCurrentMediaNotification() {
-        mMediaNotificationKey = null;
-        mMediaMetadata = null;
-        if (mMediaController != null) {
-            if (DEBUG_MEDIA) {
-                Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
-                        + mMediaController.getPackageName());
-            }
-            mMediaController.unregisterCallback(mMediaListener);
-        }
-        mMediaController = null;
-    }
-
-    private boolean sameSessions(MediaController a, MediaController b) {
-        if (a == b) return true;
-        if (a == null) return false;
-        return a.controlsSameSession(b);
-    }
 
     /**
      * Hide the album artwork that is fading out and release its bitmap.
@@ -2330,9 +2163,11 @@
         }
     };
 
+    // TODO: Move this to NotificationMediaManager.
     /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
+    @Override
     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
         Trace.beginSection("StatusBar#updateMediaMetaData");
         if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
@@ -2351,19 +2186,22 @@
             return;
         }
 
+        MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata();
+
         if (DEBUG_MEDIA) {
-            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
-                    + " metadata=" + mMediaMetadata
+            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
+                    + mMediaManager.getMediaNotificationKey()
+                    + " metadata=" + mediaMetadata
                     + " metaDataChanged=" + metaDataChanged
                     + " state=" + mState);
         }
 
         Drawable artworkDrawable = null;
-        if (mMediaMetadata != null) {
+        if (mediaMetadata != null) {
             Bitmap artworkBitmap = null;
-            artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+            artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
             if (artworkBitmap == null) {
-                artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
                 // might still be null
             }
             if (artworkBitmap != null) {
@@ -2636,7 +2474,7 @@
 
     @Override  // NotificationData.Environment
     public String getCurrentMediaNotificationKey() {
-        return mMediaNotificationKey;
+        return mMediaManager.getMediaNotificationKey();
     }
 
     public boolean isScrimSrcModeEnabled() {
@@ -3101,8 +2939,8 @@
         mStatusBarWindowManager.setForceStatusBarVisible(false);
 
         // Close any guts that might be visible
-        closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
-                -1 /* x */, -1 /* y */, true /* resetMenu */);
+        mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+                true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
 
         runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -3449,28 +3287,18 @@
         pw.println(Settings.Global.zenModeToString(mZenMode));
         pw.print("  mUseHeadsUp=");
         pw.println(mUseHeadsUp);
-        pw.print("  mKeyToRemoveOnGutsClosed=");
-        pw.println(mKeyToRemoveOnGutsClosed);
+        pw.print("  mGutsManager: ");
+        if (mGutsManager != null) {
+            mGutsManager.dump(fd, pw, args);
+        }
         if (mStatusBarView != null) {
             dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         }
 
-        pw.print("  mMediaSessionManager=");
-        pw.println(mMediaSessionManager);
-        pw.print("  mMediaNotificationKey=");
-        pw.println(mMediaNotificationKey);
-        pw.print("  mMediaController=");
-        pw.print(mMediaController);
-        if (mMediaController != null) {
-            pw.print(" state=" + mMediaController.getPlaybackState());
+        pw.println("  mMediaManager: ");
+        if (mMediaManager != null) {
+            mMediaManager.dump(fd, pw, args);
         }
-        pw.println();
-        pw.print("  mMediaMetadata=");
-        pw.print(mMediaMetadata);
-        if (mMediaMetadata != null) {
-            pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
-        }
-        pw.println();
 
         pw.println("  Panels: ");
         if (mNotificationPanel != null) {
@@ -3792,7 +3620,7 @@
             mReinflateNotificationsOnUserSwitched = false;
         }
         updateNotificationShade();
-        clearCurrentMediaNotification();
+        mMediaManager.clearCurrentMediaNotification();
         setLockscreenUser(newUserId);
     }
 
@@ -3872,10 +3700,10 @@
             if (visibleToUser) {
                 boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
                 boolean clearNotificationEffects =
-                        !isPanelFullyCollapsed() &&
+                        !isPresenterFullyCollapsed() &&
                         (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
                 int notificationLoad = mNotificationData.getActiveNotifications().size();
-                if (pinnedHeadsUp && isPanelFullyCollapsed())  {
+                if (pinnedHeadsUp && isPresenterFullyCollapsed())  {
                     notificationLoad = 1;
                 }
                 mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
@@ -4152,7 +3980,8 @@
         return mState;
     }
 
-    public boolean isPanelFullyCollapsed() {
+    @Override
+    public boolean isPresenterFullyCollapsed() {
         return mNotificationPanel.isFullyCollapsed();
     }
 
@@ -4735,7 +4564,7 @@
 
     public void onClosingFinished() {
         runPostCollapseRunnables();
-        if (!isPanelFullyCollapsed()) {
+        if (!isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
             // the closing
             mStatusBarWindowManager.setStatusBarFocusable(true);
@@ -5600,10 +5429,6 @@
 
     protected int mZenMode;
 
-    // which notification is currently being longpress-examined by the user
-    private NotificationGuts mNotificationGutsExposed;
-    private MenuItem mGutsMenuItem;
-
     protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
@@ -5614,8 +5439,6 @@
 
     protected boolean mVrMode;
 
-    private Set<String> mNonBlockablePkgs;
-
     public boolean isDeviceInteractive() {
         return mDeviceInteractive;
     }
@@ -6147,26 +5970,8 @@
         return mGroupManager;
     }
 
-    public boolean isMediaNotification(NotificationData.Entry entry) {
-        // TODO: confirm that there's a valid media key
-        return entry.getExpandedContentView() != null &&
-               entry.getExpandedContentView()
-                       .findViewById(com.android.internal.R.id.media_actions) != null;
-    }
-
-    // The button in the guts that links to the system notification settings for that app
-    private void startAppNotificationSettingsActivity(String packageName, final int appUid,
-            final NotificationChannel channel) {
-        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
-        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
-        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        if (channel != null) {
-            intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
-        }
-        startNotificationGutsIntent(intent, appUid);
-    }
-
-    private void startNotificationGutsIntent(final Intent intent, final int appUid) {
+    @Override
+    public void startNotificationGutsIntent(final Intent intent, final int appUid) {
         dismissKeyguardThenExecute(() -> {
             AsyncTask.execute(() -> {
                 TaskStackBuilder.create(mContext)
@@ -6189,231 +5994,8 @@
         }
     }
 
-    private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
-        row.inflateGuts();
-        row.setGutsView(item);
-        final StatusBarNotification sbn = row.getStatusBarNotification();
-        row.setTag(sbn.getPackageName());
-        final NotificationGuts guts = row.getGuts();
-        guts.setClosedListener((NotificationGuts g) -> {
-            if (!g.willBeRemoved() && !row.isRemoved()) {
-                mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
-            }
-            if (mNotificationGutsExposed == g) {
-                mNotificationGutsExposed = null;
-                mGutsMenuItem = null;
-            }
-            String key = sbn.getKey();
-            if (key.equals(mKeyToRemoveOnGutsClosed)) {
-                mKeyToRemoveOnGutsClosed = null;
-                removeNotification(key, mLatestRankingMap);
-            }
-        });
-
-        View gutsView = item.getGutsView();
-        if (gutsView instanceof NotificationSnooze) {
-            NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
-            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
-            snoozeGuts.setStatusBarNotification(sbn);
-            snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
-            guts.setHeightChangedListener((NotificationGuts g) -> {
-                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
-            });
-        }
-
-        if (gutsView instanceof NotificationInfo) {
-            final UserHandle userHandle = sbn.getUser();
-            PackageManager pmUser = getPackageManagerForUser(mContext,
-                    userHandle.getIdentifier());
-            final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
-                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-            final String pkg = sbn.getPackageName();
-            NotificationInfo info = (NotificationInfo) gutsView;
-            // Settings link is only valid for notifications that specify a user, unless this is the
-            // system user.
-            NotificationInfo.OnSettingsClickListener onSettingsClick = null;
-            if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
-                onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
-                    mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
-                    guts.resetFalsingCheck();
-                    startAppNotificationSettingsActivity(pkg, appUid, channel);
-                };
-            }
-            final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
-                    Intent intent) -> {
-                mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS);
-                guts.resetFalsingCheck();
-                startNotificationGutsIntent(intent, sbn.getUid());
-            };
-            final View.OnClickListener onDoneClick = (View v) -> {
-                saveAndCloseNotificationMenu(row, guts, v);
-            };
-            final NotificationInfo.CheckSaveListener checkSaveListener =
-                    (Runnable saveImportance) -> {
-                // If the user has security enabled, show challenge if the setting is changed.
-                if (isLockscreenPublicMode(userHandle.getIdentifier())
-                        && (mState == StatusBarState.KEYGUARD
-                                || mState == StatusBarState.SHADE_LOCKED)) {
-                    onLockedNotificationImportanceChange(() -> {
-                        saveImportance.run();
-                        return true;
-                    });
-                } else {
-                    saveImportance.run();
-                }
-            };
-
-            ArraySet<NotificationChannel> channels = new ArraySet<>();
-            channels.add(row.getEntry().channel);
-            if (row.isSummaryWithChildren()) {
-                // If this is a summary, then add in the children notification channels for the
-                // same user and pkg.
-                final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
-                final int numChildren = childrenRows.size();
-                for (int i = 0; i < numChildren; i++) {
-                    final ExpandableNotificationRow childRow = childrenRows.get(i);
-                    final NotificationChannel childChannel = childRow.getEntry().channel;
-                    final StatusBarNotification childSbn = childRow.getStatusBarNotification();
-                    if (childSbn.getUser().equals(userHandle) &&
-                            childSbn.getPackageName().equals(pkg)) {
-                        channels.add(childChannel);
-                    }
-                }
-            }
-            try {
-                info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
-                        row.getEntry().channel.getImportance(), sbn, onSettingsClick,
-                        onAppSettingsClick, onDoneClick, checkSaveListener,
-                        mNonBlockablePkgs);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        }
-    }
-
-    private void saveAndCloseNotificationMenu(
-            ExpandableNotificationRow row, NotificationGuts guts, View done) {
-        guts.resetFalsingCheck();
-        int[] rowLocation = new int[2];
-        int[] doneLocation = new int[2];
-        row.getLocationOnScreen(rowLocation);
-        done.getLocationOnScreen(doneLocation);
-
-        final int centerX = done.getWidth() / 2;
-        final int centerY = done.getHeight() / 2;
-        final int x = doneLocation[0] - rowLocation[0] + centerX;
-        final int y = doneLocation[1] - rowLocation[1] + centerY;
-        closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
-                true /* removeControls */, x, y, true /* resetMenu */);
-    }
-
     protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        return new ExpandableNotificationRow.LongPressListener() {
-            @Override
-            public boolean onLongPress(View v, final int x, final int y,
-                    MenuItem item) {
-                if (!(v instanceof ExpandableNotificationRow)) {
-                    return false;
-                }
-
-                if (v.getWindowToken() == null) {
-                    Log.e(TAG, "Trying to show notification guts, but not attached to window");
-                    return false;
-                }
-
-                final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-                if (row.isDark()) {
-                    return false;
-                }
-                v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-                if (row.areGutsExposed()) {
-                    closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
-                            true /* removeControls */, -1 /* x */, -1 /* y */,
-                            true /* resetMenu */);
-                    return true;
-                }
-                bindGuts(row, item);
-                NotificationGuts guts = row.getGuts();
-
-                // Assume we are a status_bar_notification_row
-                if (guts == null) {
-                    // This view has no guts. Examples are the more card or the dismiss all view
-                    return false;
-                }
-
-                mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
-
-                // ensure that it's laid but not visible until actually laid out
-                guts.setVisibility(View.INVISIBLE);
-                // Post to ensure the the guts are properly laid out.
-                guts.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (row.getWindowToken() == null) {
-                            Log.e(TAG, "Trying to show notification guts, but not attached to "
-                                    + "window");
-                            return;
-                        }
-                        closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                                true /* removeControls */, -1 /* x */, -1 /* y */,
-                                false /* resetMenu */);
-                        guts.setVisibility(View.VISIBLE);
-                        final double horz = Math.max(guts.getWidth() - x, x);
-                        final double vert = Math.max(guts.getHeight() - y, y);
-                        final float r = (float) Math.hypot(horz, vert);
-                        final Animator a
-                                = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
-                        a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                        a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-                        a.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                super.onAnimationEnd(animation);
-                                // Move the notification view back over the menu
-                                row.resetTranslation();
-                            }
-                        });
-                        a.start();
-                        final boolean needsFalsingProtection =
-                                (mState == StatusBarState.KEYGUARD &&
-                                !mAccessibilityManager.isTouchExplorationEnabled());
-                        guts.setExposed(true /* exposed */, needsFalsingProtection);
-                        row.closeRemoteInput();
-                        mStackScroller.onHeightChanged(row, true /* needsAnimation */);
-                        mNotificationGutsExposed = guts;
-                        mGutsMenuItem = item;
-                    }
-                });
-                return true;
-            }
-        };
-    }
-
-    /**
-     * Returns the exposed NotificationGuts or null if none are exposed.
-     */
-    public NotificationGuts getExposedGuts() {
-        return mNotificationGutsExposed;
-    }
-
-    /**
-     * Closes guts or notification menus that might be visible and saves any changes.
-     *
-     * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
-     * @param force true if guts should be closed regardless of state (used for snooze only).
-     * @param removeControls true if controls (e.g. info) should be closed.
-     * @param x if closed based on touch location, this is the x touch location.
-     * @param y if closed based on touch location, this is the y touch location.
-     * @param resetMenu if any notification menus that might be revealed should be closed.
-     */
-    public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
-            int x, int y, boolean resetMenu) {
-        if (mNotificationGutsExposed != null) {
-            mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
-        }
-        if (resetMenu) {
-            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
-        }
+        return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
     }
 
     @Override
@@ -6802,7 +6384,7 @@
                 if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
                     // Release the HUN notification to the shade.
 
-                    if (isPanelFullyCollapsed()) {
+                    if (isPresenterFullyCollapsed()) {
                         HeadsUpManager.setIsClickedNotification(row, true);
                     }
                     //
@@ -6934,7 +6516,7 @@
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
-                closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+                mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
                         true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
             }
         }
@@ -7156,8 +6738,8 @@
         }
         mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
         mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
-        if (key.equals(mKeyToRemoveOnGutsClosed)) {
-            mKeyToRemoveOnGutsClosed = null;
+        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+            mGutsManager.setKeyToRemoveOnGutsClosed(null);
             Log.w(TAG, "Notification that was kept for guts was updated. " + key);
         }
 
@@ -7355,4 +6937,43 @@
             mNavigationBar.getBarTransitions().setAutoDim(true);
         }
     };
+
+    public NotificationGutsManager getGutsManager() {
+        return mGutsManager;
+    }
+
+    @Override
+    public boolean isPresenterLocked() {
+        return mState == StatusBarState.KEYGUARD;
+    }
+
+    @Override
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    @Override
+    public NotificationData getNotificationData() {
+        return mNotificationData;
+    }
+
+    @Override
+    public RankingMap getLatestRankingMap() {
+        return mLatestRankingMap;
+    }
+
+    final NotificationInfo.CheckSaveListener mCheckSaveListener =
+            (Runnable saveImportance, StatusBarNotification sbn) -> {
+                // If the user has security enabled, show challenge if the setting is changed.
+                if (isLockscreenPublicMode(sbn.getUser().getIdentifier()) && (
+                        mState == StatusBarState.KEYGUARD
+                                || mState == StatusBarState.SHADE_LOCKED)) {
+                    onLockedNotificationImportanceChange(() -> {
+                        saveImportance.run();
+                        return true;
+                    });
+                } else {
+                    saveImportance.run();
+                }
+            };
 }
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 1e14626..efe049a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1343,7 +1343,7 @@
         }
 
         // Check if we need to clear any snooze leavebehinds
-        NotificationGuts guts = mStatusBar.getExposedGuts();
+        NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
         if (guts != null && !isTouchInView(ev, guts)
                 && guts.getGutsContent() instanceof NotificationSnooze) {
             NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
@@ -2508,12 +2508,13 @@
         }
         // Check if we need to clear any snooze leavebehinds
         boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
-        NotificationGuts guts = mStatusBar.getExposedGuts();
+        NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
         if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
                 && !scrollWantsIt) {
             mCheckForLeavebehind = false;
-            mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
-                    false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+            mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
         }
         if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
             mCheckForLeavebehind = true;
@@ -3065,7 +3066,7 @@
             }
             if (!childWasSwipedOut) {
                 Rect clipBounds = child.getClipBounds();
-                childWasSwipedOut = clipBounds.height() == 0;
+                childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
             }
             int animationType = childWasSwipedOut
                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
@@ -3355,8 +3356,9 @@
 
     public void checkSnoozeLeavebehind() {
         if (mCheckForLeavebehind) {
-            mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
-                    false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+            mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
             mCheckForLeavebehind = false;
         }
     }
@@ -4438,8 +4440,9 @@
                 // of the panel early.
                 handleChildDismissed(view);
             }
-            mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
-                    false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+            mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
             handleMenuCoveredOrDismissed();
         }
 
@@ -4524,7 +4527,7 @@
         }
 
         public void closeControlsIfOutsideTouch(MotionEvent ev) {
-            NotificationGuts guts = mStatusBar.getExposedGuts();
+            NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
             View view = null;
             if (guts != null && !guts.getGutsContent().isLeavebehind()) {
                 // Only close visible guts if they're not a leavebehind.
@@ -4536,8 +4539,9 @@
             }
             if (view != null && !isTouchInView(ev, view)) {
                 // Touch was outside visible guts / menu notification, close what's visible
-                mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
-                        true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+                mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */,
+                        false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+                        false /* resetMenu */);
                 resetExposedMenuView(true /* animate */, true /* force */);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f91e45d..27bf534 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -169,16 +169,23 @@
         protected boolean persistBoolean(boolean value) {
             final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                     : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+            boolean shouldSendBroadcast = false;
             for (int i = 0; i < mInfo.services.length; i++) {
                 ComponentName componentName = new ComponentName(mInfo.packageName,
                         mInfo.services[i].name);
-                mPm.setComponentEnabledSetting(componentName, desiredState,
-                        PackageManager.DONT_KILL_APP);
+
+                if (mPm.getComponentEnabledSetting(componentName) != desiredState) {
+                    mPm.setComponentEnabledSetting(componentName, desiredState,
+                            PackageManager.DONT_KILL_APP);
+                    shouldSendBroadcast = true;
+                }
             }
-            final String pkg = mInfo.packageName;
-            final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
-                    pkg != null ? Uri.fromParts("package", pkg, null) : null);
-            getContext().sendBroadcast(intent);
+            if (shouldSendBroadcast) {
+                final String pkg = mInfo.packageName;
+                final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
+                        pkg != null ? Uri.fromParts("package", pkg, null) : null);
+                getContext().sendBroadcast(intent);
+            }
             return true;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 88fa659..6721938 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -745,7 +745,7 @@
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
                 mNotificationChannel.getImportance(), mSbn, null, null, null,
-                (Runnable saveImportance) -> {
+                (Runnable saveImportance, StatusBarNotification sbn) -> {
                 },
                 Collections.singleton(TEST_PACKAGE_NAME));
 
@@ -762,7 +762,7 @@
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
                 mNotificationChannel.getImportance(), mSbn, null, null, null,
-                (Runnable saveImportance) -> {
+                (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
                 },
                 Collections.singleton(TEST_PACKAGE_NAME));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 842a5b8..86ef857 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3889,7 +3889,7 @@
             }
 
             if (app.info.isPrivilegedApp() &&
-                    !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+                    SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
                 runtimeFlags |= Zygote.DISABLE_VERIFIER;
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
             }
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index c3fed17..76b4679 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -41,10 +41,8 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.wm.WindowManagerService;
-
 import java.io.PrintWriter;
 
 /**
@@ -236,24 +234,33 @@
         final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
         mOccluded = false;
         mDismissingKeyguardActivity = null;
-        final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
-        for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = display.getChildAt(stackNdx);
 
-            // Only the focused stack top activity may control occluded state
-            if (mStackSupervisor.isFocusedStack(stack)) {
+        for (int displayNdx = mStackSupervisor.getChildCount() - 1; displayNdx >= 0; displayNdx--) {
+            final ActivityDisplay display = mStackSupervisor.getChildAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
 
-                // A dismissing activity occludes Keyguard in the insecure case for legacy reasons.
-                final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
-                mOccluded = stack.topActivityOccludesKeyguard()
-                        || (topDismissing != null
-                                && stack.topRunningActivityLocked() == topDismissing
-                                && canShowWhileOccluded(true /* dismissKeyguard */,
-                                        false /* showWhenLocked */));
-            }
-            if (mDismissingKeyguardActivity == null
-                    && stack.getTopDismissingKeyguardActivity() != null) {
-                mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+                // Only the top activity of the focused stack on the default display may control
+                // occluded state.
+                if (display.mDisplayId == DEFAULT_DISPLAY
+                        && mStackSupervisor.isFocusedStack(stack)) {
+
+                    // A dismissing activity occludes Keyguard in the insecure case for legacy
+                    // reasons.
+                    final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+                    mOccluded =
+                            stack.topActivityOccludesKeyguard()
+                                    || (topDismissing != null
+                                            && stack.topRunningActivityLocked() == topDismissing
+                                            && canShowWhileOccluded(
+                                                    true /* dismissKeyguard */,
+                                                    false /* showWhenLocked */));
+                }
+
+                if (mDismissingKeyguardActivity == null
+                        && stack.getTopDismissingKeyguardActivity() != null) {
+                    mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+                }
             }
         }
         mOccluded |= mWindowManager.isShowingDream();
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index 025ff0b..296743b 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -16,15 +16,19 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+
 import com.android.internal.util.RingBufferIndices;
 import com.android.server.job.controllers.JobStatus;
 
@@ -57,7 +61,7 @@
     public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason) {
         int index = mEventIndices.add();
         mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
-        mEventTimes[index] = SystemClock.elapsedRealtime();
+        mEventTimes[index] = sElapsedRealtimeClock.millis();
         mEventUids[index] = uid;
         mEventTags[index] = tag;
         mEventJobIds[index] = jobId;
@@ -125,9 +129,9 @@
         }
 
         public DataSet() {
-            mStartUptimeTime = SystemClock.uptimeMillis();
-            mStartElapsedTime = SystemClock.elapsedRealtime();
-            mStartClockTime = System.currentTimeMillis();
+            mStartUptimeTime = sUptimeMillisClock.millis();
+            mStartElapsedTime = sElapsedRealtimeClock.millis();
+            mStartClockTime = sSystemClock.millis();
         }
 
         private PackageEntry getOrCreateEntry(int uid, String pkg) {
@@ -376,20 +380,20 @@
     }
 
     public void notePending(JobStatus job) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         job.madePending = now;
         rebatchIfNeeded(now);
         mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
     }
 
     public void noteNonpending(JobStatus job) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
         rebatchIfNeeded(now);
     }
 
     public void noteActive(JobStatus job) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         job.madeActive = now;
         rebatchIfNeeded(now);
         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
@@ -402,7 +406,7 @@
     }
 
     public void noteInactive(JobStatus job, int stopReason) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
             mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
                     stopReason);
@@ -431,7 +435,7 @@
         if (cur == null && last == null) {
             return 0;
         }
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         long time = 0;
         if (cur != null) {
             time += cur.getActiveTime(now) + cur.getPendingTime(now);
@@ -445,8 +449,8 @@
     }
 
     public void dump(PrintWriter pw, String prefix, int filterUid) {
-        final long now = SystemClock.uptimeMillis();
-        final long nowEllapsed = SystemClock.elapsedRealtime();
+        final long now = sUptimeMillisClock.millis();
+        final long nowEllapsed = sElapsedRealtimeClock.millis();
         final DataSet total;
         if (mLastDataSets[0] != null) {
             total = new DataSet(mLastDataSets[0]);
@@ -470,7 +474,7 @@
             return false;
         }
         pw.println("  Job history:");
-        final long now = SystemClock.elapsedRealtime();
+        final long now = sElapsedRealtimeClock.millis();
         for (int i=0; i<size; i++) {
             final int index = mEventIndices.indexOf(i);
             final int uid = mEventUids[index];
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 78aa2f9..b549768 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -19,24 +19,15 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
+import android.app.job.IJobScheduler;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
-import android.app.job.IJobScheduler;
 import android.app.job.JobWorkItem;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -46,8 +37,8 @@
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryStats;
@@ -55,8 +46,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -71,6 +62,7 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.ArrayUtils;
@@ -93,6 +85,16 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
 /**
  * Responsible for taking jobs representing work to be performed by a client app, and determining
  * based on the criteria specified when that job should be run against the client application's
@@ -117,6 +119,12 @@
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
 
+    @VisibleForTesting
+    public static Clock sSystemClock = Clock.systemUTC();
+    @VisibleForTesting
+    public static Clock sUptimeMillisClock = SystemClock.uptimeMillisClock();
+    @VisibleForTesting
+    public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
 
     /** Global local for all job scheduler state. */
     final Object mLock = new Object();
@@ -960,7 +968,7 @@
             if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
                 // When we reach clock sanity, recalculate the temporal windows
                 // of all affected jobs.
-                if (mJobs.clockNowValidToInflate(System.currentTimeMillis())) {
+                if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
                     Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
 
                     // We've done our job now, so stop watching the time.
@@ -1068,7 +1076,7 @@
         if (!jobStatus.isPreparedLocked()) {
             Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
         }
-        jobStatus.enqueueTime = SystemClock.elapsedRealtime();
+        jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
         final boolean update = mJobs.add(jobStatus);
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
@@ -1156,7 +1164,7 @@
      * @see #maybeQueueReadyJobsForExecutionLocked
      */
     private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
-        final long elapsedNowMillis = SystemClock.elapsedRealtime();
+        final long elapsedNowMillis = sElapsedRealtimeClock.millis();
         final JobInfo job = failureToReschedule.getJob();
 
         final long initialBackoffMillis = job.getInitialBackoffMillis();
@@ -1200,7 +1208,7 @@
                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
         JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
                 JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
-                failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis());
+                failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
         for (int ic=0; ic<mControllers.size(); ic++) {
             StateController controller = mControllers.get(ic);
             controller.rescheduleForFailureLocked(newJob, failureToReschedule);
@@ -1220,7 +1228,7 @@
      * recurring job.
      */
     private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
-        final long elapsedNow = SystemClock.elapsedRealtime();
+        final long elapsedNow = sElapsedRealtimeClock.millis();
         // Compute how much of the period is remaining.
         long runEarly = 0L;
 
@@ -1239,7 +1247,7 @@
         }
         return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
                 newLatestRuntimeElapsed, 0 /* backoffAttempt */,
-                System.currentTimeMillis() /* lastSuccessfulRunTime */,
+                sSystemClock.millis() /* lastSuccessfulRunTime */,
                 periodicToReschedule.getLastFailedRunTime());
     }
 
@@ -2367,8 +2375,8 @@
         }
 
         final int filterUidFinal = UserHandle.getAppId(filterUid);
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long nowUptime = SystemClock.uptimeMillis();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        final long nowUptime = sUptimeMillisClock.millis();
         synchronized (mLock) {
             mConstants.dump(pw);
             pw.println();
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index a7e674b..ac7297e 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -16,11 +16,13 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.ActivityManager;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,7 +36,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Slog;
@@ -202,7 +203,7 @@
             mRunningCallback = new JobCallback();
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
-                            (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
+                            (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
             Uri[] triggeredUris = null;
             if (job.changedUris != null) {
                 triggeredUris = new Uri[job.changedUris.size()];
@@ -217,7 +218,7 @@
             mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                     isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
-            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
+            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
 
             // Once we'e begun executing a job, we by definition no longer care whether
             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
@@ -428,7 +429,7 @@
             sb.append("Caller no longer running");
             if (cb.mStoppedReason != null) {
                 sb.append(", last stopped ");
-                TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
+                TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
                 sb.append(" because: ");
                 sb.append(cb.mStoppedReason);
             }
@@ -457,7 +458,7 @@
                             sb.append("Ignoring timeout of no longer active job");
                             if (jc.mStoppedReason != null) {
                                 sb.append(", stopped ");
-                                TimeUtils.formatDuration(SystemClock.elapsedRealtime()
+                                TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
                                         - jc.mStoppedTime, sb);
                                 sb.append(" because: ");
                                 sb.append(jc.mStoppedReason);
@@ -740,7 +741,7 @@
     private void applyStoppedReasonLocked(String reason) {
         if (reason != null && mStoppedReason == null) {
             mStoppedReason = reason;
-            mStoppedTime = SystemClock.elapsedRealtime();
+            mStoppedTime = sElapsedRealtimeClock.millis();
             if (mRunningCallback != null) {
                 mRunningCallback.mStoppedReason = mStoppedReason;
                 mRunningCallback.mStoppedTime = mStoppedTime;
@@ -777,7 +778,7 @@
         }
         Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
         mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
-        mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
+        mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
     }
 
 
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index aa9f77c..1af3b39 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -16,20 +16,23 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+
 import android.app.ActivityManager;
 import android.app.IActivityManager;
-import android.content.ComponentName;
 import android.app.job.JobInfo;
+import android.content.ComponentName;
 import android.content.Context;
+import android.net.NetworkRequest;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.PersistableBundle;
 import android.os.Process;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
-import android.util.AtomicFile;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -37,11 +40,16 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.BitUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.IoThread;
 import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
 import com.android.server.job.controllers.JobStatus;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -53,10 +61,6 @@
 import java.util.List;
 import java.util.Set;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 /**
  * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
  * reference, so none of the functions in this class should make a copy.
@@ -141,7 +145,7 @@
         // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
         // settle into normal operation.
         mXmlTimestamp = mJobsFile.getLastModifiedTime();
-        mRtcGood = (System.currentTimeMillis() > mXmlTimestamp);
+        mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
 
         readJobMapFromDisk(mJobSet, mRtcGood);
     }
@@ -161,7 +165,7 @@
      */
     public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
             final ArrayList<JobStatus> toRemove) {
-        final long elapsedNow = SystemClock.elapsedRealtime();
+        final long elapsedNow = sElapsedRealtimeClock.millis();
 
         // Find the jobs that need to be fixed up, collecting them for post-iteration
         // replacement with their new versions
@@ -323,7 +327,7 @@
     private final Runnable mWriteRunnable = new Runnable() {
         @Override
         public void run() {
-            final long startElapsed = SystemClock.elapsedRealtime();
+            final long startElapsed = sElapsedRealtimeClock.millis();
             final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
             synchronized (mLock) {
                 // Clone the jobs so we can release the lock before writing.
@@ -338,7 +342,7 @@
             }
             writeJobsMapImpl(storeCopy);
             if (DEBUG) {
-                Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
+                Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
                         - startElapsed) + "ms");
             }
         }
@@ -454,17 +458,12 @@
          */
         private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
-            if (jobStatus.needsAnyConnectivity()) {
-                out.attribute(null, "connectivity", Boolean.toString(true));
-            }
-            if (jobStatus.needsMeteredConnectivity()) {
-                out.attribute(null, "metered", Boolean.toString(true));
-            }
-            if (jobStatus.needsUnmeteredConnectivity()) {
-                out.attribute(null, "unmetered", Boolean.toString(true));
-            }
-            if (jobStatus.needsNonRoamingConnectivity()) {
-                out.attribute(null, "not-roaming", Boolean.toString(true));
+            if (jobStatus.hasConnectivityConstraint()) {
+                final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+                out.attribute(null, "net-capabilities", Long.toString(
+                        BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+                out.attribute(null, "net-transport-types", Long.toString(
+                        BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
             }
             if (jobStatus.hasIdleConstraint()) {
                 out.attribute(null, "idle", Boolean.toString(true));
@@ -497,8 +496,8 @@
                 Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
             }
 
-            final long nowRTC = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowRTC = sSystemClock.millis();
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             if (jobStatus.hasDeadlineConstraint()) {
                 // Wall clock deadline.
                 final long deadlineWallclock = (utcJobTimes == null)
@@ -538,7 +537,7 @@
      */
     private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
             long nowElapsed) {
-        final long nowWallclock = System.currentTimeMillis();
+        final long nowWallclock = sSystemClock.millis();
         final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
                 ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
                 : JobStatus.NO_EARLIEST_RUNTIME;
@@ -581,7 +580,7 @@
                 synchronized (mLock) {
                     jobs = readJobMapImpl(fis, rtcGood);
                     if (jobs != null) {
-                        long now = SystemClock.elapsedRealtime();
+                        long now = sElapsedRealtimeClock.millis();
                         IActivityManager am = ActivityManager.getService();
                         for (int i=0; i<jobs.size(); i++) {
                             JobStatus js = jobs.get(i);
@@ -754,7 +753,7 @@
                 return null;
             }
 
-            final long elapsedNow = SystemClock.elapsedRealtime();
+            final long elapsedNow = sElapsedRealtimeClock.millis();
             Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
 
             if (XML_TAG_PERIODIC.equals(parser.getName())) {
@@ -862,22 +861,38 @@
         }
 
         private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
-            String val = parser.getAttributeValue(null, "connectivity");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+            String val;
+
+            final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
+            final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
+            if (netCapabilities != null && netTransportTypes != null) {
+                final NetworkRequest request = new NetworkRequest.Builder().build();
+                // We're okay throwing NFE here; caught by caller
+                request.networkCapabilities.setCapabilities(
+                        BitUtils.unpackBits(Long.parseLong(netCapabilities)));
+                request.networkCapabilities.setTransportTypes(
+                        BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
+                jobBuilder.setRequiredNetwork(request);
+            } else {
+                // Read legacy values
+                val = parser.getAttributeValue(null, "connectivity");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+                }
+                val = parser.getAttributeValue(null, "metered");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
+                }
+                val = parser.getAttributeValue(null, "unmetered");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+                }
+                val = parser.getAttributeValue(null, "not-roaming");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
+                }
             }
-            val = parser.getAttributeValue(null, "metered");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
-            }
-            val = parser.getAttributeValue(null, "unmetered");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
-            }
-            val = parser.getAttributeValue(null, "not-roaming");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
-            }
+
             val = parser.getAttributeValue(null, "idle");
             if (val != null) {
                 jobBuilder.setRequiresDeviceIdle(true);
@@ -937,8 +952,8 @@
         private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
                 throws NumberFormatException {
             // Pull out execution time data.
-            final long nowWallclock = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowWallclock = sSystemClock.millis();
+            final long nowElapsed = sElapsedRealtimeClock.millis();
 
             long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
             long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 9111969..76ff834 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -16,13 +16,14 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -203,7 +204,7 @@
                 if (Intent.ACTION_BATTERY_LOW.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Battery life too low to do work. @ "
-                                + SystemClock.elapsedRealtime());
+                                + sElapsedRealtimeClock.millis());
                     }
                     // If we get this action, the battery is discharging => it isn't plugged in so
                     // there's no work to cancel. We track this variable for the case where it is
@@ -213,14 +214,14 @@
                 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Battery life healthy enough to do work. @ "
-                                + SystemClock.elapsedRealtime());
+                                + sElapsedRealtimeClock.millis());
                     }
                     mBatteryHealthy = true;
                     maybeReportNewChargingStateLocked();
                 } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Received charging intent, fired @ "
-                                + SystemClock.elapsedRealtime());
+                                + sElapsedRealtimeClock.millis());
                     }
                     mCharging = true;
                     maybeReportNewChargingStateLocked();
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index ddee345..da28769 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -54,7 +54,6 @@
     private final ConnectivityManager mConnManager;
     private final NetworkPolicyManager mNetPolicyManager;
     private boolean mConnected;
-    private boolean mValidated;
 
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
@@ -79,7 +78,7 @@
         mConnManager = mContext.getSystemService(ConnectivityManager.class);
         mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
 
-        mConnected = mValidated = false;
+        mConnected = false;
 
         mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
         mNetPolicyManager.registerListener(mNetPolicyListener);
@@ -149,32 +148,21 @@
     }
 
     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
+        // TODO: consider matching against non-active networks
+
         final int jobUid = jobStatus.getSourceUid();
         final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
         final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
         final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
-
-        final NetworkCapabilities capabilities = (network != null)
-                ? mConnManager.getNetworkCapabilities(network) : null;
+        final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
 
         final boolean connected = (info != null) && info.isConnected();
-        final boolean validated = (capabilities != null)
-                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
+                .satisfiedByNetworkCapabilities(capabilities);
         final boolean sane = isSane(jobStatus, capabilities);
-        final boolean connectionUsable = connected && validated && sane;
 
-        final boolean metered = connected && (capabilities != null)
-                && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        final boolean unmetered = connected && (capabilities != null)
-                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        final boolean notRoaming = connected && (info != null)
-                && !info.isRoaming();
-
-        boolean changed = false;
-        changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
-        changed |= jobStatus.setMeteredConstraintSatisfied(metered);
-        changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
-        changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
+        final boolean changed = jobStatus
+                .setConnectivityConstraintSatisfied(connected && satisfied && sane);
 
         // Pass along the evaluated network for job to use; prevents race
         // conditions as default routes change over time, and opens the door to
@@ -185,17 +173,13 @@
         // overall state of connectivity constraint satisfiability.
         if (jobUid == Process.SYSTEM_UID) {
             mConnected = connected;
-            mValidated = validated;
         }
 
         if (DEBUG) {
             Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
-                    + " for " + jobStatus + ": usable=" + connectionUsable
-                    + " connected=" + connected
-                    + " validated=" + validated
-                    + " metered=" + metered
-                    + " unmetered=" + unmetered
-                    + " notRoaming=" + notRoaming);
+                    + " for " + jobStatus + ": connected=" + connected
+                    + " satisfied=" + satisfied
+                    + " sane=" + sane);
         }
         return changed;
     }
@@ -292,8 +276,6 @@
     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         pw.print("Connectivity: connected=");
         pw.print(mConnected);
-        pw.print(" validated=");
-        pw.println(mValidated);
         pw.print("Tracking ");
         pw.print(mTrackedJobs.size());
         pw.println(":");
@@ -304,10 +286,7 @@
                 js.printUniqueId(pw);
                 pw.print(" from ");
                 UserHandle.formatUid(pw, js.getSourceUid());
-                pw.print(": C="); pw.print(js.needsAnyConnectivity());
-                pw.print(": M="); pw.print(js.needsMeteredConnectivity());
-                pw.print(": UM="); pw.print(js.needsUnmeteredConnectivity());
-                pw.print(": NR="); pw.println(js.needsNonRoamingConnectivity());
+                pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
             }
         }
     }
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 9eda046..7bde174 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -16,7 +16,7 @@
 
 package com.android.server.job.controllers;
 
-import java.io.PrintWriter;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -33,6 +32,8 @@
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
+import java.io.PrintWriter;
+
 public final class IdleController extends StateController {
     private static final String TAG = "IdleController";
 
@@ -169,7 +170,7 @@
                 // when the screen goes off or dreaming starts, we schedule the
                 // alarm that will tell us when we have decided the device is
                 // truly idle.
-                final long nowElapsed = SystemClock.elapsedRealtime();
+                final long nowElapsed = sElapsedRealtimeClock.millis();
                 final long when = nowElapsed + mInactivityIdleThreshold;
                 if (DEBUG) {
                     Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
@@ -182,7 +183,7 @@
                 // idle time starts now. Do not set mIdle if screen is on.
                 if (!mIdle && !mScreenOn) {
                     if (DEBUG) {
-                        Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
+                        Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
                     }
                     mIdle = true;
                     reportNewIdleState(mIdle);
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 46ed84e..a5906cb 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.job.JobInfo;
@@ -25,7 +27,6 @@
 import android.net.Network;
 import android.net.Uri;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.Time;
 import android.util.ArraySet;
@@ -64,19 +65,12 @@
     static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
     static final int CONSTRAINT_TIMING_DELAY = 1<<31;
     static final int CONSTRAINT_DEADLINE = 1<<30;
-    static final int CONSTRAINT_UNMETERED = 1<<29;
     static final int CONSTRAINT_CONNECTIVITY = 1<<28;
     static final int CONSTRAINT_APP_NOT_IDLE = 1<<27;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25;
-    static final int CONSTRAINT_NOT_ROAMING = 1<<24;
-    static final int CONSTRAINT_METERED = 1<<23;
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22;
 
-    static final int CONNECTIVITY_MASK =
-            CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY |
-            CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED;
-
     // Soft override: ignore constraints like time that don't affect API availability
     public static final int OVERRIDE_SOFT = 1;
     // Full override: ignore all constraints including API-affecting like connectivity
@@ -264,27 +258,9 @@
 
         int requiredConstraints = job.getConstraintFlags();
 
-        switch (job.getNetworkType()) {
-            case JobInfo.NETWORK_TYPE_NONE:
-                // No constraint.
-                break;
-            case JobInfo.NETWORK_TYPE_ANY:
-                requiredConstraints |= CONSTRAINT_CONNECTIVITY;
-                break;
-            case JobInfo.NETWORK_TYPE_UNMETERED:
-                requiredConstraints |= CONSTRAINT_UNMETERED;
-                break;
-            case JobInfo.NETWORK_TYPE_NOT_ROAMING:
-                requiredConstraints |= CONSTRAINT_NOT_ROAMING;
-                break;
-            case JobInfo.NETWORK_TYPE_METERED:
-                requiredConstraints |= CONSTRAINT_METERED;
-                break;
-            default:
-                Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType());
-                break;
+        if (job.getRequiredNetwork() != null) {
+            requiredConstraints |= CONSTRAINT_CONNECTIVITY;
         }
-
         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
             requiredConstraints |= CONSTRAINT_TIMING_DELAY;
         }
@@ -365,7 +341,7 @@
      */
     public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
             int sourceUserId, String tag) {
-        final long elapsedNow = SystemClock.elapsedRealtime();
+        final long elapsedNow = sElapsedRealtimeClock.millis();
         final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
         if (job.isPeriodic()) {
             latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
@@ -610,25 +586,9 @@
 
     /** Does this job have any sort of networking constraint? */
     public boolean hasConnectivityConstraint() {
-        return (requiredConstraints&CONNECTIVITY_MASK) != 0;
-    }
-
-    public boolean needsAnyConnectivity() {
         return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
     }
 
-    public boolean needsUnmeteredConnectivity() {
-        return (requiredConstraints&CONSTRAINT_UNMETERED) != 0;
-    }
-
-    public boolean needsMeteredConnectivity() {
-        return (requiredConstraints&CONSTRAINT_METERED) != 0;
-    }
-
-    public boolean needsNonRoamingConnectivity() {
-        return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0;
-    }
-
     public boolean hasChargingConstraint() {
         return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
     }
@@ -725,18 +685,6 @@
         return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
     }
 
-    boolean setUnmeteredConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_UNMETERED, state);
-    }
-
-    boolean setMeteredConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_METERED, state);
-    }
-
-    boolean setNotRoamingConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state);
-    }
-
     boolean setAppNotIdleConstraintSatisfied(boolean state) {
         return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state);
     }
@@ -817,12 +765,9 @@
                 && notRestrictedInBg;
     }
 
-    static final int CONSTRAINTS_OF_INTEREST =
-            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
-            CONSTRAINT_TIMING_DELAY |
-            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
-            CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
-            CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
+    static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
+            | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
+            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
     // Soft override covers all non-"functional" constraints
     static final int SOFT_OVERRIDE_CONSTRAINTS =
@@ -870,15 +815,14 @@
         sb.append(getSourceUid());
         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
                 || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
-            long now = SystemClock.elapsedRealtime();
+            long now = sElapsedRealtimeClock.millis();
             sb.append(" TIME=");
             formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
             sb.append(":");
             formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
         }
-        if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
-            sb.append(" NET=");
-            sb.append(job.getNetworkType());
+        if (job.getRequiredNetwork() != null) {
+            sb.append(" NET");
         }
         if (job.isRequireCharging()) {
             sb.append(" CHARGING");
@@ -985,15 +929,6 @@
         if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
             pw.print(" CONNECTIVITY");
         }
-        if ((constraints&CONSTRAINT_UNMETERED) != 0) {
-            pw.print(" UNMETERED");
-        }
-        if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
-            pw.print(" NOT_ROAMING");
-        }
-        if ((constraints&CONSTRAINT_METERED) != 0) {
-            pw.print(" METERED");
-        }
         if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
             pw.print(" APP_NOT_IDLE");
         }
@@ -1084,11 +1019,13 @@
                 pw.print(prefix); pw.println("  Granted URI permissions:");
                 uriPerms.dump(pw, prefix + "  ");
             }
-            if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
-                pw.print(prefix); pw.print("  Network type: "); pw.println(job.getNetworkType());
+            if (job.getRequiredNetwork() != null) {
+                pw.print(prefix); pw.print("  Network type: ");
+                pw.println(job.getRequiredNetwork());
             }
             if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                pw.print(prefix); pw.print("  Network bytes: "); pw.println(totalNetworkBytes);
+                pw.print(prefix); pw.print("  Network bytes: ");
+                pw.println(totalNetworkBytes);
             }
             if (job.getMinLatencyMillis() != 0) {
                 pw.print(prefix); pw.print("  Minimum latency: ");
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index c24e563..84782f5 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -16,11 +16,12 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -154,13 +155,13 @@
             if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Available storage too low to do work. @ "
-                            + SystemClock.elapsedRealtime());
+                            + sElapsedRealtimeClock.millis());
                 }
                 mStorageLow = true;
             } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Available stoage high enough to do work. @ "
-                            + SystemClock.elapsedRealtime());
+                            + sElapsedRealtimeClock.millis());
                 }
                 mStorageLow = false;
                 maybeReportNewStorageState();
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index ee4c606..cb9e43a 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -16,10 +16,11 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
 import android.content.Context;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Slog;
@@ -84,7 +85,7 @@
             // pattern of having a job with a 0 deadline constraint ("run immediately").
             // Unlike most controllers, once one of our constraints has been satisfied, it
             // will never be unsatisfied (our time base can not go backwards).
-            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
                 return;
             } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
@@ -157,7 +158,7 @@
             long nextExpiryTime = Long.MAX_VALUE;
             int nextExpiryUid = 0;
             String nextExpiryPackageName = null;
-            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
 
             Iterator<JobStatus> it = mTrackedJobs.iterator();
             while (it.hasNext()) {
@@ -201,7 +202,7 @@
      */
     private void checkExpiredDelaysAndResetAlarm() {
         synchronized (mLock) {
-            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             long nextDelayTime = Long.MAX_VALUE;
             int nextDelayUid = 0;
             String nextDelayPackageName = null;
@@ -283,7 +284,7 @@
     }
 
     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
-        final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime();
+        final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis();
         if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
             return earliestWakeupTimeElapsed;
         }
@@ -328,9 +329,9 @@
 
     @Override
     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         pw.print("Alarms: now=");
-        pw.print(SystemClock.elapsedRealtime());
+        pw.print(sElapsedRealtimeClock.millis());
         pw.println();
         pw.print("Next delay alarm in ");
         TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f61cec9..1e9fab5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -835,7 +835,8 @@
         // alarm restrictions
         final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
         // total silence restrictions
-        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+                || areAllBehaviorSoundsMuted();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
             final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
@@ -855,9 +856,11 @@
         }
     }
 
+
     @VisibleForTesting
     protected void applyRestrictions(boolean mute, int usage) {
         final String[] exceptionPackages = null; // none (for now)
+
         mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                 exceptionPackages);
@@ -866,6 +869,12 @@
                 exceptionPackages);
     }
 
+    private boolean areAllBehaviorSoundsMuted() {
+        return !mConfig.allowAlarms  && !mConfig.allowMediaSystemOther && !mConfig.allowReminders
+                && !mConfig.allowCalls && !mConfig.allowMessages && !mConfig.allowEvents
+                && !mConfig.allowRepeatCallers;
+    }
+
     private void applyZenToRingerMode() {
         if (mAudioManager == null) return;
         // force the ringer mode into compliance
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 50ac409..29f48ee 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -110,9 +110,9 @@
             return false;
         }
 
-        // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+        // We do not dexopt a priv-app package when pm.dexopt.priv-apps-oob is true.
         if (pkg.isPrivileged()) {
-            return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+            return !SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false);
         }
 
         return true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6a0ea4ee..3b547d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9598,7 +9598,7 @@
 
         if (Build.IS_DEBUGGABLE &&
                 pkg.isPrivileged() &&
-                !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+                SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
             PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
         }
 
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 7a2e630..3707a5e 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -58,7 +58,7 @@
 
     public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
         if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
-        if (mStatusBarInternal.isGlobalActionsDisabled()) {
+        if (mStatusBarInternal != null && mStatusBarInternal.isGlobalActionsDisabled()) {
             return;
         }
         mKeyguardShowing = keyguardShowing;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7837940..a11d282 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -59,6 +59,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
@@ -66,6 +68,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
@@ -123,11 +126,8 @@
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
 
-import static com.android.server.wm.proto.WindowManagerPolicyProto.STABLE_BOUNDS;
-
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerInternal.SleepToken;
 import android.app.ActivityThread;
@@ -210,6 +210,7 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.DisplayFrames;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IApplicationToken;
@@ -596,47 +597,6 @@
 
     PointerLocationView mPointerLocationView;
 
-    // The current size of the screen; really; extends into the overscan area of
-    // the screen and doesn't account for any system elements like the status bar.
-    int mOverscanScreenLeft, mOverscanScreenTop;
-    int mOverscanScreenWidth, mOverscanScreenHeight;
-    // The current visible size of the screen; really; (ir)regardless of whether the status
-    // bar can be hidden but not extending into the overscan area.
-    int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
-    int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight;
-    // Like mOverscanScreen*, but allowed to move into the overscan region where appropriate.
-    int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;
-    int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;
-    // The current size of the screen; these may be different than (0,0)-(dw,dh)
-    // if the status bar can't be hidden; in that case it effectively carves out
-    // that area of the display from all other windows.
-    int mRestrictedScreenLeft, mRestrictedScreenTop;
-    int mRestrictedScreenWidth, mRestrictedScreenHeight;
-    // During layout, the current screen borders accounting for any currently
-    // visible system UI elements.
-    int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
-    // For applications requesting stable content insets, these are them.
-    int mStableLeft, mStableTop, mStableRight, mStableBottom;
-    // For applications requesting stable content insets but have also set the
-    // fullscreen window flag, these are the stable dimensions without the status bar.
-    int mStableFullscreenLeft, mStableFullscreenTop;
-    int mStableFullscreenRight, mStableFullscreenBottom;
-    // During layout, the current screen borders with all outer decoration
-    // (status bar, input method dock) accounted for.
-    int mCurLeft, mCurTop, mCurRight, mCurBottom;
-    // During layout, the frame in which content should be displayed
-    // to the user, accounting for all screen decoration except for any
-    // space they deem as available for other content.  This is usually
-    // the same as mCur*, but may be larger if the screen decor has supplied
-    // content insets.
-    int mContentLeft, mContentTop, mContentRight, mContentBottom;
-    // During layout, the frame in which voice content should be displayed
-    // to the user, accounting for all screen decoration except for any
-    // space they deem as available for other content.
-    int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom;
-    // During layout, the current screen borders along which input method
-    // windows are placed.
-    int mDockLeft, mDockTop, mDockRight, mDockBottom;
     // During layout, the layer at which the doc window is placed.
     int mDockLayer;
     // During layout, this is the layer of the status bar.
@@ -734,18 +694,11 @@
 
     Display mDisplay;
 
-    private int mDisplayRotation;
-
     int mLandscapeRotation = 0;  // default landscape rotation
     int mSeascapeRotation = 0;   // "other" landscape rotation, 180 degrees from mLandscapeRotation
     int mPortraitRotation = 0;   // default portrait rotation
     int mUpsideDownRotation = 0; // "other" portrait rotation
 
-    int mOverscanLeft = 0;
-    int mOverscanTop = 0;
-    int mOverscanRight = 0;
-    int mOverscanBottom = 0;
-
     // What we do when the user long presses on home
     private int mLongPressOnHomeBehavior;
 
@@ -1061,7 +1014,7 @@
             View.NAVIGATION_BAR_UNHIDE,
             View.NAVIGATION_BAR_TRANSLUCENT,
             StatusBarManager.WINDOW_NAVIGATION_BAR,
-            WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
+            FLAG_TRANSLUCENT_NAVIGATION,
             View.NAVIGATION_BAR_TRANSPARENT);
 
     private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
@@ -2068,6 +2021,7 @@
         context.registerReceiver(mMultiuserReceiver, filter);
 
         // monitor for system gestures
+        // TODO(multi-display): Needs to be display specific.
         mSystemGestures = new SystemGesturesPointerEventListener(context,
                 new SystemGesturesPointerEventListener.Callbacks() {
                     @Override
@@ -2314,17 +2268,6 @@
         return mForceDefaultOrientation;
     }
 
-    @Override
-    public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
-        // TODO(multi-display): Define policy for secondary displays.
-        if (display.getDisplayId() == DEFAULT_DISPLAY) {
-            mOverscanLeft = left;
-            mOverscanTop = top;
-            mOverscanRight = right;
-            mOverscanBottom = bottom;
-        }
-    }
-
     public void updateSettings() {
         ContentResolver resolver = mContext.getContentResolver();
         boolean updateRotation = false;
@@ -4321,12 +4264,16 @@
     }
 
     @Override
+    // TODO: Should probably be moved into DisplayFrames.
     public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
-            Rect outStableInsets, Rect outOutsets) {
+            DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets) {
         final int fl = PolicyControl.getWindowFlags(null, attrs);
         final int sysuiVis = PolicyControl.getSystemUiVisibility(null, attrs);
         final int systemUiVisibility = (sysuiVis | attrs.subtreeSystemUiVisibility);
+        final int displayRotation = displayFrames.mRotation;
+        final int displayWidth = displayFrames.mDisplayWidth;
+        final int displayHeight = displayFrames.mDisplayHeight;
 
         final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
         if (useOutsets) {
@@ -4349,34 +4296,33 @@
             int availRight, availBottom;
             if (canHideNavigationBar() &&
                     (systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
-                availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                availRight = displayFrames.mUnrestricted.right;
+                availBottom = displayFrames.mUnrestricted.bottom;
             } else {
-                availRight = mRestrictedScreenLeft + mRestrictedScreenWidth;
-                availBottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+                availRight = displayFrames.mRestricted.right;
+                availBottom = displayFrames.mRestricted.bottom;
             }
+            outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top,
+                    availRight - displayFrames.mStable.right,
+                    availBottom - displayFrames.mStable.bottom);
+
             if ((systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
                 if ((fl & FLAG_FULLSCREEN) != 0) {
-                    outContentInsets.set(mStableFullscreenLeft, mStableFullscreenTop,
-                            availRight - mStableFullscreenRight,
-                            availBottom - mStableFullscreenBottom);
+                    outContentInsets.set(displayFrames.mStableFullscreen.left,
+                            displayFrames.mStableFullscreen.top,
+                            availRight - displayFrames.mStableFullscreen.right,
+                            availBottom - displayFrames.mStableFullscreen.bottom);
                 } else {
-                    outContentInsets.set(mStableLeft, mStableTop,
-                            availRight - mStableRight, availBottom - mStableBottom);
+                    outContentInsets.set(outStableInsets);
                 }
             } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
                 outContentInsets.setEmpty();
-            } else if ((systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
-                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) {
-                outContentInsets.set(mCurLeft, mCurTop,
-                        availRight - mCurRight, availBottom - mCurBottom);
             } else {
-                outContentInsets.set(mCurLeft, mCurTop,
-                        availRight - mCurRight, availBottom - mCurBottom);
+                outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top,
+                        availRight - displayFrames.mCurrent.right,
+                        availBottom - displayFrames.mCurrent.bottom);
             }
 
-            outStableInsets.set(mStableLeft, mStableTop,
-                    availRight - mStableRight, availBottom - mStableBottom);
             if (taskBounds != null) {
                 calculateRelevantTaskInsets(taskBounds, outContentInsets,
                         displayWidth, displayHeight);
@@ -4413,68 +4359,11 @@
 
     /** {@inheritDoc} */
     @Override
-    public void beginLayoutLw(int displayId, int displayWidth, int displayHeight,
-            int displayRotation, int uiMode) {
-        final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY;
-        mDisplayRotation = displayRotation;
-        final int overscanLeft, overscanTop, overscanRight, overscanBottom;
-        if (isDefaultDisplay) {
-            switch (displayRotation) {
-                case Surface.ROTATION_90:
-                    overscanLeft = mOverscanTop;
-                    overscanTop = mOverscanRight;
-                    overscanRight = mOverscanBottom;
-                    overscanBottom = mOverscanLeft;
-                    break;
-                case Surface.ROTATION_180:
-                    overscanLeft = mOverscanRight;
-                    overscanTop = mOverscanBottom;
-                    overscanRight = mOverscanLeft;
-                    overscanBottom = mOverscanTop;
-                    break;
-                case Surface.ROTATION_270:
-                    overscanLeft = mOverscanBottom;
-                    overscanTop = mOverscanLeft;
-                    overscanRight = mOverscanTop;
-                    overscanBottom = mOverscanRight;
-                    break;
-                default:
-                    overscanLeft = mOverscanLeft;
-                    overscanTop = mOverscanTop;
-                    overscanRight = mOverscanRight;
-                    overscanBottom = mOverscanBottom;
-                    break;
-            }
-        } else {
-            overscanLeft = 0;
-            overscanTop = 0;
-            overscanRight = 0;
-            overscanBottom = 0;
-        }
-        mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
-        mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
-        mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
-        mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
-        mSystemLeft = 0;
-        mSystemTop = 0;
-        mSystemRight = displayWidth;
-        mSystemBottom = displayHeight;
-        mUnrestrictedScreenLeft = overscanLeft;
-        mUnrestrictedScreenTop = overscanTop;
-        mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
-        mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
-        mRestrictedScreenLeft = mUnrestrictedScreenLeft;
-        mRestrictedScreenTop = mUnrestrictedScreenTop;
-        mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
-        mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
-        mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
-                = mCurLeft = mUnrestrictedScreenLeft;
-        mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
-                = mCurTop = mUnrestrictedScreenTop;
-        mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
-                = mCurRight = displayWidth - overscanRight;
-        mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
-                = mCurBottom = displayHeight - overscanBottom;
+    public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
+        displayFrames.onBeginLayout();
+        // TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
+        mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
+        mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
         mDockLayer = 0x10000000;
         mStatusBarLayer = -1;
 
@@ -4484,13 +4373,13 @@
         final Rect of = mTmpOverscanFrame;
         final Rect vf = mTmpVisibleFrame;
         final Rect dcf = mTmpDecorFrame;
-        pf.left = df.left = of.left = vf.left = mDockLeft;
-        pf.top = df.top = of.top = vf.top = mDockTop;
-        pf.right = df.right = of.right = vf.right = mDockRight;
-        pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;
+        vf.set(displayFrames.mDock);
+        of.set(displayFrames.mDock);
+        df.set(displayFrames.mDock);
+        pf.set(displayFrames.mDock);
         dcf.setEmpty();  // Decor frame N/A for system bars.
 
-        if (isDefaultDisplay) {
+        if (displayFrames.mDisplayId == DEFAULT_DISPLAY) {
             // For purposes of putting out fake window up to steal focus, we will
             // drive nav being hidden only by whether it is requested.
             final int sysui = mLastSystemUiFlags;
@@ -4509,10 +4398,9 @@
                     && mStatusBar.getAttrs().height == MATCH_PARENT
                     && mStatusBar.getAttrs().width == MATCH_PARENT;
 
-            // When the navigation bar isn't visible, we put up a fake
-            // input window to catch all touch events.  This way we can
-            // detect when the user presses anywhere to bring back the nav
-            // bar and ensure the application doesn't see the event.
+            // When the navigation bar isn't visible, we put up a fake input window to catch all
+            // touch events. This way we can detect when the user presses anywhere to bring back the
+            // nav bar and ensure the application doesn't see the event.
             if (navVisible || navAllowedHidden) {
                 if (mInputConsumer != null) {
                     mHandler.sendMessage(
@@ -4528,30 +4416,32 @@
                 InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
             }
 
-            // For purposes of positioning and showing the nav bar, if we have
-            // decided that it can't be hidden (because of the screen aspect ratio),
-            // then take that into account.
+            // For purposes of positioning and showing the nav bar, if we have decided that it can't
+            // be hidden (because of the screen aspect ratio), then take that into account.
             navVisible |= !canHideNavigationBar();
 
-            boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
-                    displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
-                    navAllowedHidden, statusBarExpandedNotKeyguard);
-            if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
-                    mDockLeft, mDockTop, mDockRight, mDockBottom));
-            updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);
+            boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf,
+                    navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
+            if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
+            updateSysUiVisibility |= layoutStatusBar(
+                    displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing);
             if (updateSysUiVisibility) {
                 updateSystemUiVisibilityLw();
             }
         }
-        layoutScreenDecorWindows(displayId, displayWidth, displayHeight, pf, df, dcf);
+        layoutScreenDecorWindows(displayFrames, pf, df, dcf);
     }
 
-    private void layoutScreenDecorWindows(int displayId, int displayWidth, int displayHeight,
-            Rect pf, Rect df, Rect dcf) {
+    private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
         if (mScreenDecorWindows.isEmpty()) {
             return;
         }
 
+        final int displayId = displayFrames.mDisplayId;
+        final Rect dockFrame = displayFrames.mDock;
+        final int displayHeight = displayFrames.mDisplayHeight;
+        final int displayWidth = displayFrames.mDisplayWidth;
+
         for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) {
             final WindowState w = mScreenDecorWindows.valueAt(i);
             if (w.getDisplayId() != displayId || !w.isVisibleLw()) {
@@ -4568,10 +4458,10 @@
                 // Docked at left or top.
                 if (frame.bottom >= displayHeight) {
                     // Docked left.
-                    mDockLeft = Math.max(frame.right, mDockLeft);
+                    dockFrame.left = Math.max(frame.right, dockFrame.left);
                 } else if (frame.right >= displayWidth ) {
                     // Docked top.
-                    mDockTop = Math.max(frame.bottom, mDockTop);
+                    dockFrame.top = Math.max(frame.bottom, dockFrame.top);
                 } else {
                     Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
                             + " not docked on left or top of display. frame=" + frame
@@ -4581,10 +4471,10 @@
                 // Docked at right or bottom.
                 if (frame.top <= 0) {
                     // Docked right.
-                    mDockRight = Math.min(frame.left, mDockRight);
+                    dockFrame.right = Math.min(frame.left, dockFrame.right);
                 } else if (frame.left <= 0) {
                     // Docked bottom.
-                    mDockBottom = Math.min(frame.top, mDockBottom);
+                    dockFrame.bottom = Math.min(frame.top, dockFrame.bottom);
                 } else {
                     Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
                             + " not docked on right or bottom" + " of display. frame=" + frame
@@ -4598,194 +4488,165 @@
             }
         }
 
-        mContentTop = mSystemTop = mVoiceContentTop = mCurTop = mRestrictedScreenTop = mDockTop;
-        mContentLeft = mSystemLeft = mVoiceContentLeft = mCurLeft = mRestrictedScreenLeft
-                = mRestrictedOverscanScreenLeft = mDockLeft;
-        mContentBottom = mSystemBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
-        mContentRight = mSystemRight = mVoiceContentRight = mCurRight = mDockRight;
-
-        mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
-        mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
-        mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
-        mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
+        displayFrames.mRestricted.set(dockFrame);
+        displayFrames.mCurrent.set(dockFrame);
+        displayFrames.mVoiceContent.set(dockFrame);
+        displayFrames.mSystem.set(dockFrame);
+        displayFrames.mContent.set(dockFrame);
+        displayFrames.mRestrictedOverscan.set(dockFrame);
     }
 
-    private boolean layoutStatusBar(Rect pf, Rect df, Rect of, Rect vf, Rect dcf, int sysui,
-            boolean isKeyguardShowing) {
+    private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
+            Rect dcf, int sysui, boolean isKeyguardShowing) {
         // decide where the status bar goes ahead of time
-        if (mStatusBar != null) {
-            // apply any navigation bar insets
-            pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-            pf.top = df.top = of.top = mUnrestrictedScreenTop;
-            pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight
-                    + mUnrestrictedScreenTop;
-            vf.left = mStableLeft;
-            vf.top = mStableTop;
-            vf.right = mStableRight;
-            vf.bottom = mStableBottom;
+        if (mStatusBar == null) {
+            return false;
+        }
+        // apply any navigation bar insets
+        of.set(displayFrames.mUnrestricted);
+        df.set(displayFrames.mUnrestricted);
+        pf.set(displayFrames.mUnrestricted);
+        vf.set(displayFrames.mStable);
 
-            mStatusBarLayer = mStatusBar.getSurfaceLayer();
+        mStatusBarLayer = mStatusBar.getSurfaceLayer();
 
-            // Let the status bar determine its size.
-            mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
-                    vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
-                    dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
+        // Let the status bar determine its size.
+        mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
+                vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
+                dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
 
-            // For layout, the status bar is always at the top with our fixed height.
-            mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
+        // For layout, the status bar is always at the top with our fixed height.
+        displayFrames.mStable.top = displayFrames.mUnrestricted.top + mStatusBarHeight;
 
-            boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
-            boolean statusBarTranslucent = (sysui
-                    & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
-            if (!isKeyguardShowing) {
-                statusBarTranslucent &= areTranslucentBarsAllowed();
-            }
+        boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
+        boolean statusBarTranslucent = (sysui
+                & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
+        if (!isKeyguardShowing) {
+            statusBarTranslucent &= areTranslucentBarsAllowed();
+        }
 
-            // If the status bar is hidden, we don't want to cause
-            // windows behind it to scroll.
-            if (mStatusBar.isVisibleLw() && !statusBarTransient) {
-                // Status bar may go away, so the screen area it occupies
-                // is available to apps but just covering them when the
-                // status bar is visible.
-                mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
+        // If the status bar is hidden, we don't want to cause windows behind it to scroll.
+        if (mStatusBar.isVisibleLw() && !statusBarTransient) {
+            // Status bar may go away, so the screen area it occupies is available to apps but just
+            // covering them when the status bar is visible.
+            final Rect dockFrame = displayFrames.mDock;
+            dockFrame.top = displayFrames.mStable.top;
+            displayFrames.mContent.set(dockFrame);
+            displayFrames.mVoiceContent.set(dockFrame);
 
-                mContentTop = mVoiceContentTop = mCurTop = mDockTop;
-                mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
-                mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
-                mContentRight = mVoiceContentRight = mCurRight = mDockRight;
+            if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
+                    "dock=%s content=%s cur=%s", dockFrame.toString(),
+                    displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
 
-                if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " +
-                        String.format(
-                                "dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]",
-                                mDockLeft, mDockTop, mDockRight, mDockBottom,
-                                mContentLeft, mContentTop, mContentRight, mContentBottom,
-                                mCurLeft, mCurTop, mCurRight, mCurBottom));
-            }
-            if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw()
-                    && !statusBarTransient && !statusBarTranslucent
+            if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
                     && !mStatusBarController.wasRecentlyTranslucent()) {
-                // If the opaque status bar is currently requested to be visible,
-                // and not in the process of animating on or off, then
-                // we can tell the app that it is covered by it.
-                mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
-            }
-            if (mStatusBarController.checkHiddenLw()) {
-                return true;
+                // If the opaque status bar is currently requested to be visible, and not in the
+                // process of animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.top = displayFrames.mStable.top;
             }
         }
-        return false;
+        return mStatusBarController.checkHiddenLw();
     }
 
-    private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation,
-            int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf,
+    private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf,
             boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
             boolean statusBarExpandedNotKeyguard) {
-        if (mNavigationBar != null) {
-            boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
-            // Force the navigation bar to its appropriate place and
-            // size.  We need to do this directly, instead of relying on
-            // it to bubble up from the nav bar, because this needs to
-            // change atomically with screen rotations.
-            mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
-                    displayRotation);
-            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
-                // It's a system nav bar or a portrait screen; nav bar goes on bottom.
-                int top = displayHeight - overscanBottom
-                        - getNavigationBarHeight(displayRotation, uiMode);
-                mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
-                mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
-                if (transientNavBarShowing) {
-                    mNavigationBarController.setBarShowingLw(true);
-                } else if (navVisible) {
-                    mNavigationBarController.setBarShowingLw(true);
-                    mDockBottom = mTmpNavigationFrame.top;
-                    mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
-                    mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
-                } else {
-                    // We currently want to hide the navigation UI - unless we expanded the status
-                    // bar.
-                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
-                }
-                if (navVisible && !navTranslucent && !navAllowedHidden
-                        && !mNavigationBar.isAnimatingLw()
-                        && !mNavigationBarController.wasRecentlyTranslucent()) {
-                    // If the opaque nav bar is currently requested to be visible,
-                    // and not in the process of animating on or off, then
-                    // we can tell the app that it is covered by it.
-                    mSystemBottom = mTmpNavigationFrame.top;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
-                // Landscape screen; nav bar goes to the right.
-                int left = displayWidth - overscanRight
-                        - getNavigationBarWidth(displayRotation, uiMode);
-                mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
-                mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
-                if (transientNavBarShowing) {
-                    mNavigationBarController.setBarShowingLw(true);
-                } else if (navVisible) {
-                    mNavigationBarController.setBarShowingLw(true);
-                    mDockRight = mTmpNavigationFrame.left;
-                    mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
-                    mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
-                } else {
-                    // We currently want to hide the navigation UI - unless we expanded the status
-                    // bar.
-                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
-                }
-                if (navVisible && !navTranslucent && !navAllowedHidden
-                        && !mNavigationBar.isAnimatingLw()
-                        && !mNavigationBarController.wasRecentlyTranslucent()) {
-                    // If the nav bar is currently requested to be visible,
-                    // and not in the process of animating on or off, then
-                    // we can tell the app that it is covered by it.
-                    mSystemRight = mTmpNavigationFrame.left;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
-                // Seascape screen; nav bar goes to the left.
-                int right = overscanLeft + getNavigationBarWidth(displayRotation, uiMode);
-                mTmpNavigationFrame.set(overscanLeft, 0, right, displayHeight);
-                mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right;
-                if (transientNavBarShowing) {
-                    mNavigationBarController.setBarShowingLw(true);
-                } else if (navVisible) {
-                    mNavigationBarController.setBarShowingLw(true);
-                    mDockLeft = mTmpNavigationFrame.right;
-                    // TODO: not so sure about those:
-                    mRestrictedScreenLeft = mRestrictedOverscanScreenLeft = mDockLeft;
-                    mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
-                    mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
-                } else {
-                    // We currently want to hide the navigation UI - unless we expanded the status
-                    // bar.
-                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
-                }
-                if (navVisible && !navTranslucent && !navAllowedHidden
-                        && !mNavigationBar.isAnimatingLw()
-                        && !mNavigationBarController.wasRecentlyTranslucent()) {
-                    // If the nav bar is currently requested to be visible,
-                    // and not in the process of animating on or off, then
-                    // we can tell the app that it is covered by it.
-                    mSystemLeft = mTmpNavigationFrame.right;
-                }
+        if (mNavigationBar == null) {
+            return false;
+        }
+        boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+        // Force the navigation bar to its appropriate place and size. We need to do this directly,
+        // instead of relying on it to bubble up from the nav bar, because this needs to change
+        // atomically with screen rotations.
+        final int rotation = displayFrames.mRotation;
+        final int displayHeight = displayFrames.mDisplayHeight;
+        final int displayWidth = displayFrames.mDisplayWidth;
+        final Rect dockFrame = displayFrames.mDock;
+        mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
+
+        if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
+            // It's a system nav bar or a portrait screen; nav bar goes on bottom.
+            final int top = displayFrames.mUnrestricted.bottom
+                    - getNavigationBarHeight(rotation, uiMode);
+            mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
+            displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.bottom = displayFrames.mRestricted.bottom
+                        = displayFrames.mRestrictedOverscan.bottom = top;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
             }
-            // Make sure the content and current rectangles are updated to
-            // account for the restrictions from the navigation bar.
-            mContentTop = mVoiceContentTop = mCurTop = mDockTop;
-            mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
-            mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
-            mContentRight = mVoiceContentRight = mCurRight = mDockRight;
-            mStatusBarLayer = mNavigationBar.getSurfaceLayer();
-            // And compute the final frame.
-            mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
-                    mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
-                    mTmpNavigationFrame, mTmpNavigationFrame);
-            if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
-            if (mNavigationBarController.checkHiddenLw()) {
-                return true;
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the opaque nav bar is currently requested to be visible and not in the process
+                // of animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.bottom = top;
+            }
+        } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+            // Landscape screen; nav bar goes to the right.
+            final int left = displayFrames.mUnrestricted.right
+                    - getNavigationBarWidth(rotation, uiMode);
+            mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight);
+            displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.right = displayFrames.mRestricted.right
+                        = displayFrames.mRestrictedOverscan.right = left;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the nav bar is currently requested to be visible, and not in the process of
+                // animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.right = left;
+            }
+        } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+            // Seascape screen; nav bar goes to the left.
+            final int right = displayFrames.mUnrestricted.left
+                    - getNavigationBarWidth(rotation, uiMode);
+            mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight);
+            displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.left = displayFrames.mRestricted.left =
+                        displayFrames.mRestrictedOverscan.left = right;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the nav bar is currently requested to be visible, and not in the process of
+                // animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.left = right;
             }
         }
-        return false;
+
+        // Make sure the content and current rectangles are updated to account for the restrictions
+        // from the navigation bar.
+        displayFrames.mCurrent.set(dockFrame);
+        displayFrames.mVoiceContent.set(dockFrame);
+        displayFrames.mContent.set(dockFrame);
+        mStatusBarLayer = mNavigationBar.getSurfaceLayer();
+        // And compute the final frame.
+        mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
+                mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
+                mTmpNavigationFrame, mTmpNavigationFrame);
+        if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
+        return mNavigationBarController.checkHiddenLw();
     }
 
     private int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
@@ -4813,32 +4674,26 @@
         return 0;
     }
 
-    @Override
-    public void getContentRectLw(Rect r) {
-        r.set(mContentLeft, mContentTop, mContentRight, mContentBottom);
-    }
-
-    void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
-            boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf) {
+    private void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
+            boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf,
+            DisplayFrames displayFrames) {
         if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) {
-            // Here's a special case: if this attached window is a panel that is
-            // above the dock window, and the window it is attached to is below
-            // the dock window, then the frames we computed for the window it is
-            // attached to can not be used because the dock is effectively part
-            // of the underlying window and the attached window is floating on top
-            // of the whole thing.  So, we ignore the attached window and explicitly
-            // compute the frames that would be appropriate without the dock.
-            df.left = of.left = cf.left = vf.left = mDockLeft;
-            df.top = of.top = cf.top = vf.top = mDockTop;
-            df.right = of.right = cf.right = vf.right = mDockRight;
-            df.bottom = of.bottom = cf.bottom = vf.bottom = mDockBottom;
+            // Here's a special case: if this attached window is a panel that is above the dock
+            // window, and the window it is attached to is below the dock window, then the frames we
+            // computed for the window it is attached to can not be used because the dock is
+            // effectively part of the underlying window and the attached window is floating on top
+            // of the whole thing. So, we ignore the attached window and explicitly compute the
+            // frames that would be appropriate without the dock.
+            vf.set(displayFrames.mDock);
+            cf.set(displayFrames.mDock);
+            of.set(displayFrames.mDock);
+            df.set(displayFrames.mDock);
         } else {
-            // The effective display frame of the attached window depends on
-            // whether it is taking care of insetting its content.  If not,
-            // we need to use the parent's content frame so that the entire
-            // window is positioned within that content.  Otherwise we can use
-            // the overscan frame and let the attached window take care of
-            // positioning its content appropriately.
+            // The effective display frame of the attached window depends on whether it is taking
+            // care of insetting its content. If not, we need to use the parent's content frame so
+            // that the entire window is positioned within that content. Otherwise we can use the
+            // overscan frame and let the attached window take care of positioning its content
+            // appropriately.
             if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                 // Set the content frame of the attached window to the parent's decor frame
                 // (same as content frame when IME isn't present) if specifically requested by
@@ -4847,51 +4702,37 @@
                 cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
                         ? attached.getContentFrameLw() : attached.getOverscanFrameLw());
             } else {
-                // If the window is resizing, then we want to base the content
-                // frame on our attached content frame to resize...  however,
-                // things can be tricky if the attached window is NOT in resize
-                // mode, in which case its content frame will be larger.
-                // Ungh.  So to deal with that, make sure the content frame
-                // we end up using is not covering the IM dock.
+                // If the window is resizing, then we want to base the content frame on our attached
+                // content frame to resize...however, things can be tricky if the attached window is
+                // NOT in resize mode, in which case its content frame will be larger.
+                // Ungh. So to deal with that, make sure the content frame we end up using is not
+                // covering the IM dock.
                 cf.set(attached.getContentFrameLw());
                 if (attached.isVoiceInteraction()) {
-                    if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft;
-                    if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop;
-                    if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight;
-                    if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom;
+                    cf.intersectUnchecked(displayFrames.mVoiceContent);
                 } else if (attached.getSurfaceLayer() < mDockLayer) {
-                    if (cf.left < mContentLeft) cf.left = mContentLeft;
-                    if (cf.top < mContentTop) cf.top = mContentTop;
-                    if (cf.right > mContentRight) cf.right = mContentRight;
-                    if (cf.bottom > mContentBottom) cf.bottom = mContentBottom;
+                    cf.intersectUnchecked(displayFrames.mContent);
                 }
             }
             df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
             of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
             vf.set(attached.getVisibleFrameLw());
         }
-        // The LAYOUT_IN_SCREEN flag is used to determine whether the attached
-        // window should be positioned relative to its parent or the entire
-        // screen.
-        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
-                ? attached.getFrameLw() : df);
+        // The LAYOUT_IN_SCREEN flag is used to determine whether the attached window should be
+        // positioned relative to its parent or the entire screen.
+        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
     }
 
-    private void applyStableConstraints(int sysui, int fl, Rect r) {
-        if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
-            // If app is requesting a stable layout, don't let the
-            // content insets go below the stable values.
-            if ((fl & FLAG_FULLSCREEN) != 0) {
-                if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft;
-                if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop;
-                if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight;
-                if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom;
-            } else {
-                if (r.left < mStableLeft) r.left = mStableLeft;
-                if (r.top < mStableTop) r.top = mStableTop;
-                if (r.right > mStableRight) r.right = mStableRight;
-                if (r.bottom > mStableBottom) r.bottom = mStableBottom;
-            }
+    private void applyStableConstraints(int sysui, int fl, Rect r, DisplayFrames displayFrames) {
+        if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) == 0) {
+            return;
+        }
+        // If app is requesting a stable layout, don't let the content insets go below the stable
+        // values.
+        if ((fl & FLAG_FULLSCREEN) != 0) {
+            r.intersectUnchecked(displayFrames.mStableFullscreen);
+        } else {
+            r.intersectUnchecked(displayFrames.mStable);
         }
     }
 
@@ -4906,7 +4747,7 @@
 
     /** {@inheritDoc} */
     @Override
-    public void layoutWindowLw(WindowState win, WindowState attached) {
+    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
         // We've already done the navigation bar, status bar, and all screen decor windows. If the
         // status bar can receive input, we need to layout it again to accommodate for the IME
         // window.
@@ -4920,9 +4761,10 @@
                 (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
         if (needsToOffsetInputMethodTarget) {
             if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
-            offsetInputMethodWindowLw(mLastInputMethodWindow);
+            offsetInputMethodWindowLw(mLastInputMethodWindow, displayFrames);
         }
 
+        final int type = attrs.type;
         final int fl = PolicyControl.getWindowFlags(win, attrs);
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
@@ -4943,120 +4785,83 @@
 
         final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
 
-        if (isDefaultDisplay) {
-            sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
-        } else {
-            sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
-        }
+        sf.set(displayFrames.mStable);
 
-        if (!isDefaultDisplay) {
-            // TODO: Need to fix this and above to take into account decor windows.
-            if (attached != null) {
-                // If this window is attached to another, our display
-                // frame is the same as the one we are attached to.
-                setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
-            } else {
-                // Give the window full screen.
-                pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                pf.right = df.right = of.right = cf.right
-                        = mOverscanScreenLeft + mOverscanScreenWidth;
-                pf.bottom = df.bottom = of.bottom = cf.bottom
-                        = mOverscanScreenTop + mOverscanScreenHeight;
-            }
-        } else if (attrs.type == TYPE_INPUT_METHOD) {
-            pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
-            pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
-            pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
+        if (type == TYPE_INPUT_METHOD) {
+            vf.set(displayFrames.mDock);
+            cf.set(displayFrames.mDock);
+            of.set(displayFrames.mDock);
+            df.set(displayFrames.mDock);
+            pf.set(displayFrames.mDock);
             // IM dock windows layout below the nav bar...
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+            pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
             // ...with content insets above the nav bar
-            cf.bottom = vf.bottom = mStableBottom;
+            cf.bottom = vf.bottom = displayFrames.mStable.bottom;
             if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
                 // The status bar forces the navigation bar while it's visible. Make sure the IME
                 // avoids the navigation bar in that case.
                 if (mNavigationBarPosition == NAV_BAR_RIGHT) {
-                    pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+                    pf.right = df.right = of.right = cf.right = vf.right =
+                            displayFrames.mStable.right;
                 } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
-                    pf.left = df.left = of.left = cf.left = vf.left = mStableLeft;
+                    pf.left = df.left = of.left = cf.left = vf.left = displayFrames.mStable.left;
                 }
             }
             // IM dock windows always go to the bottom of the screen.
             attrs.gravity = Gravity.BOTTOM;
             mDockLayer = win.getSurfaceLayer();
-        } else if (attrs.type == TYPE_VOICE_INTERACTION) {
-            pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-            pf.top = df.top = of.top = mUnrestrictedScreenTop;
-            pf.right = df.right = of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+        } else if (type == TYPE_VOICE_INTERACTION) {
+            of.set(displayFrames.mUnrestricted);
+            df.set(displayFrames.mUnrestricted);
+            pf.set(displayFrames.mUnrestricted);
             if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                cf.left = mDockLeft;
-                cf.top = mDockTop;
-                cf.right = mDockRight;
-                cf.bottom = mDockBottom;
+                cf.set(displayFrames.mDock);
             } else {
-                cf.left = mContentLeft;
-                cf.top = mContentTop;
-                cf.right = mContentRight;
-                cf.bottom = mContentBottom;
+                cf.set(displayFrames.mContent);
             }
             if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                vf.left = mCurLeft;
-                vf.top = mCurTop;
-                vf.right = mCurRight;
-                vf.bottom = mCurBottom;
+                vf.set(displayFrames.mCurrent);
             } else {
                 vf.set(cf);
             }
-        } else if (attrs.type == TYPE_WALLPAPER) {
-           layoutWallpaper(win, pf, df, of, cf);
+        } else if (type == TYPE_WALLPAPER) {
+           layoutWallpaper(displayFrames, pf, df, of, cf);
         } else if (win == mStatusBar) {
-            pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-            pf.top = df.top = of.top = mUnrestrictedScreenTop;
-            pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + mUnrestrictedScreenTop;
-            cf.left = vf.left = mStableLeft;
-            cf.top = vf.top = mStableTop;
-            cf.right = vf.right = mStableRight;
-            vf.bottom = mStableBottom;
+            of.set(displayFrames.mUnrestricted);
+            df.set(displayFrames.mUnrestricted);
+            pf.set(displayFrames.mUnrestricted);
+            cf.set(displayFrames.mStable);
+            vf.set(displayFrames.mStable);
 
             if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
-                cf.bottom = mContentBottom;
+                cf.bottom = displayFrames.mContent.bottom;
             } else {
-                cf.bottom = mDockBottom;
-                vf.bottom = mContentBottom;
+                cf.bottom = displayFrames.mDock.bottom;
+                vf.bottom = displayFrames.mContent.bottom;
             }
         } else {
-
-            // Default policy decor for the default display
-            dcf.left = mSystemLeft;
-            dcf.top = mSystemTop;
-            dcf.right = mSystemRight;
-            dcf.bottom = mSystemBottom;
-            final boolean inheritTranslucentDecor = (attrs.privateFlags
-                    & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
+            dcf.set(displayFrames.mSystem);
+            final boolean inheritTranslucentDecor =
+                    (attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
             final boolean isAppWindow =
-                    attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&
-                    attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+                    type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
             final boolean topAtRest =
                     win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
             if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
                 if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
-                        && (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0
-                        && (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0
-                        && (fl & WindowManager.LayoutParams.
-                                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
+                        && (fl & FLAG_FULLSCREEN) == 0
+                        && (fl & FLAG_TRANSLUCENT_STATUS) == 0
+                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                         && (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
                     // Ensure policy decor includes status bar
-                    dcf.top = mStableTop;
+                    dcf.top = displayFrames.mStable.top;
                 }
-                if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0
+                if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
                         && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
-                        && (fl & WindowManager.LayoutParams.
-                                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
                     // Ensure policy decor includes navigation bar
-                    dcf.bottom = mStableBottom;
-                    dcf.right = mStableRight;
+                    dcf.bottom = displayFrames.mStable.bottom;
+                    dcf.right = displayFrames.mStable.right;
                 }
             }
 
@@ -5064,117 +4869,83 @@
                     == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                             + "): IN_SCREEN, INSET_DECOR");
-                // This is the case for a normal activity window: we want it
-                // to cover all of the screen space, and it can take care of
-                // moving its contents to account for screen decorations that
-                // intrude into that space.
+                // This is the case for a normal activity window: we want it to cover all of the
+                // screen space, and it can take care of moving its contents to account for screen
+                // decorations that intrude into that space.
                 if (attached != null) {
                     // If this window is attached to another, our display
                     // frame is the same as the one we are attached to.
-                    setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
+                    setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
+                            displayFrames);
                 } else {
-                    if (attrs.type == TYPE_STATUS_BAR_PANEL
-                            || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
-                        // Status bar panels are the only windows who can go on top of
-                        // the status bar.  They are protected by the STATUS_BAR_SERVICE
-                        // permission, so they have the same privileges as the status
-                        // bar itself.
+                    if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+                        // Status bar panels are the only windows who can go on top of the status
+                        // bar. They are protected by the STATUS_BAR_SERVICE permission, so they
+                        // have the same privileges as the status bar itself.
                         //
                         // However, they should still dodge the navigation bar if it exists.
 
                         pf.left = df.left = of.left = hasNavBar
-                                ? mDockLeft : mUnrestrictedScreenLeft;
-                        pf.top = df.top = of.top = mUnrestrictedScreenTop;
+                                ? displayFrames.mDock.left : displayFrames.mUnrestricted.left;
+                        pf.top = df.top = of.top = displayFrames.mUnrestricted.top;
                         pf.right = df.right = of.right = hasNavBar
-                                ? mRestrictedScreenLeft+mRestrictedScreenWidth
-                                : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+                                ? displayFrames.mRestricted.right
+                                : displayFrames.mUnrestricted.right;
                         pf.bottom = df.bottom = of.bottom = hasNavBar
-                                ? mRestrictedScreenTop+mRestrictedScreenHeight
-                                : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                                ? displayFrames.mRestricted.bottom
+                                : displayFrames.mUnrestricted.bottom;
 
                         if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                         "Laying out status bar window: (%d,%d - %d,%d)",
                                         pf.left, pf.top, pf.right, pf.bottom));
                     } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
-                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                            && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
                         // Asking to layout into the overscan region, so give it that pure
                         // unrestricted area.
-                        pf.left = df.left = of.left = mOverscanScreenLeft;
-                        pf.top = df.top = of.top = mOverscanScreenTop;
-                        pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth;
-                        pf.bottom = df.bottom = of.bottom = mOverscanScreenTop
-                                + mOverscanScreenHeight;
+                        of.set(displayFrames.mOverscan);
+                        df.set(displayFrames.mOverscan);
+                        pf.set(displayFrames.mOverscan);
                     } else if (canHideNavigationBar()
                             && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
-                        // Asking for layout as if the nav bar is hidden, lets the
-                        // application extend into the unrestricted overscan screen area.  We
-                        // only do this for application windows to ensure no window that
-                        // can be above the nav bar can do this.
-                        pf.left = df.left = mOverscanScreenLeft;
-                        pf.top = df.top = mOverscanScreenTop;
-                        pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
-                        pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
-                        // We need to tell the app about where the frame inside the overscan
-                        // is, so it can inset its content by that amount -- it didn't ask
-                        // to actually extend itself into the overscan region.
-                        of.left = mUnrestrictedScreenLeft;
-                        of.top = mUnrestrictedScreenTop;
-                        of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                        of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                            && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+                        // Asking for layout as if the nav bar is hidden, lets the application
+                        // extend into the unrestricted overscan screen area. We only do this for
+                        // application windows to ensure no window that can be above the nav bar can
+                        // do this.
+                        df.set(displayFrames.mOverscan);
+                        pf.set(displayFrames.mOverscan);
+                        // We need to tell the app about where the frame inside the overscan is, so
+                        // it can inset its content by that amount -- it didn't ask to actually
+                        // extend itself into the overscan region.
+                        of.set(displayFrames.mUnrestricted);
                     } else {
-                        pf.left = df.left = mRestrictedOverscanScreenLeft;
-                        pf.top = df.top = mRestrictedOverscanScreenTop;
-                        pf.right = df.right = mRestrictedOverscanScreenLeft
-                                + mRestrictedOverscanScreenWidth;
-                        pf.bottom = df.bottom = mRestrictedOverscanScreenTop
-                                + mRestrictedOverscanScreenHeight;
+                        df.set(displayFrames.mRestrictedOverscan);
+                        pf.set(displayFrames.mRestrictedOverscan);
                         // We need to tell the app about where the frame inside the overscan
                         // is, so it can inset its content by that amount -- it didn't ask
                         // to actually extend itself into the overscan region.
-                        of.left = mUnrestrictedScreenLeft;
-                        of.top = mUnrestrictedScreenTop;
-                        of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                        of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                        of.set(displayFrames.mUnrestricted);
                     }
 
                     if ((fl & FLAG_FULLSCREEN) == 0) {
                         if (win.isVoiceInteraction()) {
-                            cf.left = mVoiceContentLeft;
-                            cf.top = mVoiceContentTop;
-                            cf.right = mVoiceContentRight;
-                            cf.bottom = mVoiceContentBottom;
+                            cf.set(displayFrames.mVoiceContent);
                         } else {
                             if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                                cf.left = mDockLeft;
-                                cf.top = mDockTop;
-                                cf.right = mDockRight;
-                                cf.bottom = mDockBottom;
+                                cf.set(displayFrames.mDock);
                             } else {
-                                cf.left = mContentLeft;
-                                cf.top = mContentTop;
-                                cf.right = mContentRight;
-                                cf.bottom = mContentBottom;
+                                cf.set(displayFrames.mContent);
                             }
                         }
                     } else {
-                        // Full screen windows are always given a layout that is as if the
-                        // status bar and other transient decors are gone.  This is to avoid
-                        // bad states when moving from a window that is not hding the
-                        // status bar to one that is.
-                        cf.left = mRestrictedScreenLeft;
-                        cf.top = mRestrictedScreenTop;
-                        cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
-                        cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+                        // Full screen windows are always given a layout that is as if the status
+                        // bar and other transient decors are gone. This is to avoid bad states when
+                        // moving from a window that is not hiding the status bar to one that is.
+                        cf.set(displayFrames.mRestricted);
                     }
-                    applyStableConstraints(sysUiFl, fl, cf);
+                    applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                        vf.left = mCurLeft;
-                        vf.top = mCurTop;
-                        vf.right = mCurRight;
-                        vf.bottom = mCurBottom;
+                        vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
                     }
@@ -5182,86 +4953,70 @@
             } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
                     & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
-                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
-                        "): IN_SCREEN");
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): IN_SCREEN");
                 // A window that has requested to fill the entire screen just
                 // gets everything, period.
-                if (attrs.type == TYPE_STATUS_BAR_PANEL
-                        || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
-                    pf.left = df.left = of.left = cf.left = hasNavBar
-                            ? mDockLeft : mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = hasNavBar
-                            ? mRestrictedScreenLeft + mRestrictedScreenWidth
-                            : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar
-                            ? mRestrictedScreenTop + mRestrictedScreenHeight
-                            : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
+                    if (hasNavBar) {
+                        pf.left = df.left = of.left = cf.left = displayFrames.mDock.left;
+                        pf.right = df.right = of.right = cf.right = displayFrames.mRestricted.right;
+                        pf.bottom = df.bottom = of.bottom = cf.bottom =
+                                displayFrames.mRestricted.bottom;
+                    }
                     if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                             "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
                             pf.left, pf.top, pf.right, pf.bottom));
-                } else if (attrs.type == TYPE_VOLUME_OVERLAY) {
+                } else if (type == TYPE_VOLUME_OVERLAY) {
                     // Volume overlay covers everything, including the status and navbar
-                    pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right =
-                            mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom =
-                            mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
                     if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                     "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
                                     pf.left, pf.top, pf.right, pf.bottom));
-                } else if (attrs.type == TYPE_NAVIGATION_BAR
-                        || attrs.type == TYPE_NAVIGATION_BAR_PANEL) {
+                } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
                     // The navigation bar has Real Ultimate Power.
-                    pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = mUnrestrictedScreenLeft
-                            + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop
-                            + mUnrestrictedScreenHeight;
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
                     if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                     "Laying out navigation bar window: (%d,%d - %d,%d)",
                                     pf.left, pf.top, pf.right, pf.bottom));
-                } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
-                                || attrs.type == TYPE_BOOT_PROGRESS
-                                || attrs.type == TYPE_SCREENSHOT)
+                } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
                         && ((fl & FLAG_FULLSCREEN) != 0)) {
                     // Fullscreen secure system overlays get what they ask for. Screenshot region
                     // selection overlay should also expand to full screen.
-                    pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                    pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
-                            + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
-                            + mOverscanScreenHeight;
-                } else if (attrs.type == TYPE_BOOT_PROGRESS) {
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
+                } else if (type == TYPE_BOOT_PROGRESS) {
                     // Boot progress screen always covers entire display.
-                    pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                    pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
-                            + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
-                            + mOverscanScreenHeight;
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
                 } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
-                        && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                        && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
-                    // Asking to layout into the overscan region, so give it that pure
-                    // unrestricted area.
-                    pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                    pf.right = df.right = of.right = cf.right
-                            = mOverscanScreenLeft + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom
-                            = mOverscanScreenTop + mOverscanScreenHeight;
+                        && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+                    // Asking to layout into the overscan region, so give it that pure unrestricted
+                    // area.
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
                 } else if (canHideNavigationBar()
                         && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                        && (attrs.type == TYPE_STATUS_BAR
-                            || attrs.type == TYPE_TOAST
-                            || attrs.type == TYPE_DOCK_DIVIDER
-                            || attrs.type == TYPE_VOICE_INTERACTION_STARTING
-                            || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
+                        && (type == TYPE_STATUS_BAR
+                            || type == TYPE_TOAST
+                            || type == TYPE_DOCK_DIVIDER
+                            || type == TYPE_VOICE_INTERACTION_STARTING
+                            || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
                     // Asking for layout as if the nav bar is hidden, lets the
                     // application extend into the unrestricted screen area.  We
                     // only do this for application windows (or toasts) to ensure no window that
@@ -5269,102 +5024,76 @@
                     // XXX This assumes that an app asking for this will also
                     // ask for layout in only content.  We can't currently figure out
                     // what the screen would be if only laying out to hide the nav bar.
-                    pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
-                            + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
-                            + mUnrestrictedScreenHeight;
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
                 } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
-                    pf.left = df.left = of.left = mRestrictedScreenLeft;
-                    pf.top = df.top = of.top  = mRestrictedScreenTop;
-                    pf.right = df.right = of.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = mRestrictedScreenTop
-                            + mRestrictedScreenHeight;
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
                     if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                        cf.left = mDockLeft;
-                        cf.top = mDockTop;
-                        cf.right = mDockRight;
-                        cf.bottom = mDockBottom;
+                        cf.set(displayFrames.mDock);
                     } else {
-                        cf.left = mContentLeft;
-                        cf.top = mContentTop;
-                        cf.right = mContentRight;
-                        cf.bottom = mContentBottom;
+                        cf.set(displayFrames.mContent);
                     }
                 } else {
-                    pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
-                            + mRestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
-                            + mRestrictedScreenHeight;
+                    cf.set(displayFrames.mRestricted);
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
                 }
 
-                applyStableConstraints(sysUiFl, fl, cf);
+                applyStableConstraints(sysUiFl, fl, cf,displayFrames);
 
                 if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                    vf.left = mCurLeft;
-                    vf.top = mCurTop;
-                    vf.right = mCurRight;
-                    vf.bottom = mCurBottom;
+                    vf.set(displayFrames.mCurrent);
                 } else {
                     vf.set(cf);
                 }
             } else if (attached != null) {
-                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
-                        "): attached to " + attached);
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): attached to " + attached);
                 // A child window should be placed inside of the same visible
                 // frame that its parent had.
-                setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);
+                setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
+                        displayFrames);
             } else {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
                         "): normal window");
                 // Otherwise, a normal window must be placed inside the content
                 // of all screen decorations.
-                if (attrs.type == TYPE_STATUS_BAR_PANEL || attrs.type == TYPE_VOLUME_OVERLAY) {
+                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_VOLUME_OVERLAY) {
                     // Status bar panels and the volume dialog are the only windows who can go on
-                    // top of the status bar.  They are protected by the STATUS_BAR_SERVICE
-                    // permission, so they have the same privileges as the status
-                    // bar itself.
-                    pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
-                            + mRestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
-                            + mRestrictedScreenHeight;
-                } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT) {
+                    // top of the status bar. They are protected by the STATUS_BAR_SERVICE
+                    // permission, so they have the same privileges as the status bar itself.
+                    cf.set(displayFrames.mRestricted);
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
+                } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
                     // These dialogs are stable to interim decor changes.
-                    pf.left = df.left = of.left = cf.left = mStableLeft;
-                    pf.top = df.top = of.top = cf.top = mStableTop;
-                    pf.right = df.right = of.right = cf.right = mStableRight;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mStableBottom;
+                    cf.set(displayFrames.mStable);
+                    of.set(displayFrames.mStable);
+                    df.set(displayFrames.mStable);
+                    pf.set(displayFrames.mStable);
                 } else {
-                    pf.left = mContentLeft;
-                    pf.top = mContentTop;
-                    pf.right = mContentRight;
-                    pf.bottom = mContentBottom;
+                    pf.set(displayFrames.mContent);
                     if (win.isVoiceInteraction()) {
-                        df.left = of.left = cf.left = mVoiceContentLeft;
-                        df.top = of.top = cf.top = mVoiceContentTop;
-                        df.right = of.right = cf.right = mVoiceContentRight;
-                        df.bottom = of.bottom = cf.bottom = mVoiceContentBottom;
+                        cf.set(displayFrames.mVoiceContent);
+                        of.set(displayFrames.mVoiceContent);
+                        df.set(displayFrames.mVoiceContent);
                     } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                        df.left = of.left = cf.left = mDockLeft;
-                        df.top = of.top = cf.top = mDockTop;
-                        df.right = of.right = cf.right = mDockRight;
-                        df.bottom = of.bottom = cf.bottom = mDockBottom;
+                        cf.set(displayFrames.mDock);
+                        of.set(displayFrames.mDock);
+                        df.set(displayFrames.mDock);
                     } else {
-                        df.left = of.left = cf.left = mContentLeft;
-                        df.top = of.top = cf.top = mContentTop;
-                        df.right = of.right = cf.right = mContentRight;
-                        df.bottom = of.bottom = cf.bottom = mContentBottom;
+                        cf.set(displayFrames.mContent);
+                        of.set(displayFrames.mContent);
+                        df.set(displayFrames.mContent);
                     }
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                        vf.left = mCurLeft;
-                        vf.top = mCurTop;
-                        vf.right = mCurRight;
-                        vf.bottom = mCurBottom;
+                        vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
                     }
@@ -5374,11 +5103,11 @@
 
         // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
         // Also, we don't allow windows in multi-window mode to extend out of the screen.
-        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR
+        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
                 && !win.isInMultiWindowMode()) {
             df.left = df.top = -10000;
             df.right = df.bottom = 10000;
-            if (attrs.type != TYPE_WALLPAPER) {
+            if (type != TYPE_WALLPAPER) {
                 of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
                 of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
             }
@@ -5394,7 +5123,7 @@
             osf.set(cf.left, cf.top, cf.right, cf.bottom);
             int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
             if (outset > 0) {
-                int rotation = mDisplayRotation;
+                int rotation = displayFrames.mRotation;
                 if (rotation == Surface.ROTATION_0) {
                     osf.bottom += outset;
                 } else if (rotation == Surface.ROTATION_90) {
@@ -5411,7 +5140,7 @@
 
         if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
                 + ": sim=#" + Integer.toHexString(sim)
-                + " attach=" + attached + " type=" + attrs.type
+                + " attach=" + attached + " type=" + type
                 + String.format(" flags=0x%08x", fl)
                 + " pf=" + pf.toShortString() + " df=" + df.toShortString()
                 + " of=" + of.toShortString()
@@ -5424,62 +5153,42 @@
 
         // Dock windows carve out the bottom of the screen, so normal windows
         // can't appear underneath them.
-        if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleLw()
+        if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
                 && !win.getGivenInsetsPendingLw()) {
             setLastInputMethodWindowLw(null, null);
-            offsetInputMethodWindowLw(win);
+            offsetInputMethodWindowLw(win, displayFrames);
         }
-        if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
+        if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
                 && !win.getGivenInsetsPendingLw()) {
-            offsetVoiceInputWindowLw(win);
+            offsetVoiceInputWindowLw(win, displayFrames);
         }
     }
 
-    private void layoutWallpaper(WindowState win, Rect pf, Rect df, Rect of, Rect cf) {
-
-        // The wallpaper also has Real Ultimate Power, but we want to tell
-        // it about the overscan area.
-        pf.left = df.left = mOverscanScreenLeft;
-        pf.top = df.top = mOverscanScreenTop;
-        pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
-        pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
-        of.left = cf.left = mUnrestrictedScreenLeft;
-        of.top = cf.top = mUnrestrictedScreenTop;
-        of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-        of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+    private void layoutWallpaper(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect cf) {
+        // The wallpaper has Real Ultimate Power, but we want to tell it about the overscan area.
+        df.set(displayFrames.mOverscan);
+        pf.set(displayFrames.mOverscan);
+        cf.set(displayFrames.mUnrestricted);
+        of.set(displayFrames.mUnrestricted);
     }
 
-    private void offsetInputMethodWindowLw(WindowState win) {
+    private void offsetInputMethodWindowLw(WindowState win, DisplayFrames displayFrames) {
         int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
         top += win.getGivenContentInsetsLw().top;
-        if (mContentBottom > top) {
-            mContentBottom = top;
-        }
-        if (mVoiceContentBottom > top) {
-            mVoiceContentBottom = top;
-        }
+        displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, top);
+        displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
         top = win.getVisibleFrameLw().top;
         top += win.getGivenVisibleInsetsLw().top;
-        if (mCurBottom > top) {
-            mCurBottom = top;
-        }
+        displayFrames.mCurrent.bottom = Math.min(displayFrames.mCurrent.bottom, top);
         if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
-                + mDockBottom + " mContentBottom="
-                + mContentBottom + " mCurBottom=" + mCurBottom);
+                + displayFrames.mDock.bottom + " mContentBottom="
+                + displayFrames.mContent.bottom + " mCurBottom=" + displayFrames.mCurrent.bottom);
     }
 
-    private void offsetVoiceInputWindowLw(WindowState win) {
+    private void offsetVoiceInputWindowLw(WindowState win, DisplayFrames displayFrames) {
         int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
         top += win.getGivenContentInsetsLw().top;
-        if (mVoiceContentBottom > top) {
-            mVoiceContentBottom = top;
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void finishLayoutLw() {
-        return;
+        displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
     }
 
     /** {@inheritDoc} */
@@ -8332,11 +8041,6 @@
     }
 
     @Override
-    public int getInputMethodWindowVisibleHeightLw() {
-        return mDockBottom - mCurBottom;
-    }
-
-    @Override
     public void setCurrentUserLw(int newUserId) {
         mCurrentUserId = newUserId;
         if (mKeyguardDelegate != null) {
@@ -8425,8 +8129,6 @@
     @Override
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        new Rect(mStableLeft, mStableTop, mStableRight, mStableBottom).writeToProto(proto,
-                STABLE_BOUNDS);
         proto.end(token);
     }
 
@@ -8532,58 +8234,6 @@
                 pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
         pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
                 pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
-        pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
-                pw.print(","); pw.print(mOverscanScreenTop);
-                pw.print(") "); pw.print(mOverscanScreenWidth);
-                pw.print("x"); pw.println(mOverscanScreenHeight);
-        if (mOverscanLeft != 0 || mOverscanTop != 0
-                || mOverscanRight != 0 || mOverscanBottom != 0) {
-            pw.print(prefix); pw.print("mOverscan left="); pw.print(mOverscanLeft);
-                    pw.print(" top="); pw.print(mOverscanTop);
-                    pw.print(" right="); pw.print(mOverscanRight);
-                    pw.print(" bottom="); pw.println(mOverscanBottom);
-        }
-        pw.print(prefix); pw.print("mRestrictedOverscanScreen=(");
-                pw.print(mRestrictedOverscanScreenLeft);
-                pw.print(","); pw.print(mRestrictedOverscanScreenTop);
-                pw.print(") "); pw.print(mRestrictedOverscanScreenWidth);
-                pw.print("x"); pw.println(mRestrictedOverscanScreenHeight);
-        pw.print(prefix); pw.print("mUnrestrictedScreen=("); pw.print(mUnrestrictedScreenLeft);
-                pw.print(","); pw.print(mUnrestrictedScreenTop);
-                pw.print(") "); pw.print(mUnrestrictedScreenWidth);
-                pw.print("x"); pw.println(mUnrestrictedScreenHeight);
-        pw.print(prefix); pw.print("mRestrictedScreen=("); pw.print(mRestrictedScreenLeft);
-                pw.print(","); pw.print(mRestrictedScreenTop);
-                pw.print(") "); pw.print(mRestrictedScreenWidth);
-                pw.print("x"); pw.println(mRestrictedScreenHeight);
-        pw.print(prefix); pw.print("mStableFullscreen=("); pw.print(mStableFullscreenLeft);
-                pw.print(","); pw.print(mStableFullscreenTop);
-                pw.print(")-("); pw.print(mStableFullscreenRight);
-                pw.print(","); pw.print(mStableFullscreenBottom); pw.println(")");
-        pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft);
-                pw.print(","); pw.print(mStableTop);
-                pw.print(")-("); pw.print(mStableRight);
-                pw.print(","); pw.print(mStableBottom); pw.println(")");
-        pw.print(prefix); pw.print("mSystem=("); pw.print(mSystemLeft);
-                pw.print(","); pw.print(mSystemTop);
-                pw.print(")-("); pw.print(mSystemRight);
-                pw.print(","); pw.print(mSystemBottom); pw.println(")");
-        pw.print(prefix); pw.print("mCur=("); pw.print(mCurLeft);
-                pw.print(","); pw.print(mCurTop);
-                pw.print(")-("); pw.print(mCurRight);
-                pw.print(","); pw.print(mCurBottom); pw.println(")");
-        pw.print(prefix); pw.print("mContent=("); pw.print(mContentLeft);
-                pw.print(","); pw.print(mContentTop);
-                pw.print(")-("); pw.print(mContentRight);
-                pw.print(","); pw.print(mContentBottom); pw.println(")");
-        pw.print(prefix); pw.print("mVoiceContent=("); pw.print(mVoiceContentLeft);
-                pw.print(","); pw.print(mVoiceContentTop);
-                pw.print(")-("); pw.print(mVoiceContentRight);
-                pw.print(","); pw.print(mVoiceContentBottom); pw.println(")");
-        pw.print(prefix); pw.print("mDock=("); pw.print(mDockLeft);
-                pw.print(","); pw.print(mDockTop);
-                pw.print(")-("); pw.print(mDockRight);
-                pw.print(","); pw.print(mDockBottom); pw.println(")");
         pw.print(prefix); pw.print("mDockLayer="); pw.print(mDockLayer);
                 pw.print(" mStatusBarLayer="); pw.println(mStatusBarLayer);
         pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 67d62e1..c4b810f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -104,6 +104,7 @@
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
 import static com.android.server.wm.proto.DisplayProto.ABOVE_APP_WINDOWS;
 import static com.android.server.wm.proto.DisplayProto.BELOW_APP_WINDOWS;
+import static com.android.server.wm.proto.DisplayProto.DISPLAY_FRAMES;
 import static com.android.server.wm.proto.DisplayProto.DISPLAY_INFO;
 import static com.android.server.wm.proto.DisplayProto.DOCKED_STACK_DIVIDER_CONTROLLER;
 import static com.android.server.wm.proto.DisplayProto.DPI;
@@ -147,6 +148,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.view.IInputMethodClient;
+import android.view.DisplayFrames;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -222,6 +224,8 @@
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
     private final Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    DisplayFrames mDisplayFrames;
+
     /**
      * For default display it contains real metrics, empty for others.
      * @see WindowManagerService#createWatermarkInTransaction()
@@ -285,7 +289,6 @@
     private boolean mLastWallpaperVisible = false;
 
     private Rect mBaseDisplayRect = new Rect();
-    private Rect mContentRect = new Rect();
 
     // Accessed directly by all users.
     private boolean mLayoutNeeded;
@@ -545,7 +548,7 @@
                 w.mLayoutNeeded = false;
                 w.prelayout();
                 final boolean firstLayout = !w.isLaidOut();
-                mService.mPolicy.layoutWindowLw(w, null);
+                mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
                 w.mLayoutSeq = mService.mLayoutSeq;
 
                 // If this is the first layout, we need to initialize the last inset values as
@@ -586,7 +589,7 @@
                 }
                 w.mLayoutNeeded = false;
                 w.prelayout();
-                mService.mPolicy.layoutWindowLw(w, w.getParentWindow());
+                mService.mPolicy.layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
                 w.mLayoutSeq = mService.mLayoutSeq;
                 if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
                         + " mContainingFrame=" + w.mContainingFrame
@@ -758,6 +761,7 @@
         display.getMetrics(mDisplayMetrics);
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
         mService = service;
+        mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo);
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service, this);
         mPinnedStackControllerLocked = new PinnedStackController(service, this);
@@ -1129,6 +1133,13 @@
         return true;
     }
 
+    void configureDisplayPolicy() {
+        mService.mPolicy.setInitialDisplaySize(getDisplay(),
+                mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
+
+        mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+    }
+
     /**
      * Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
      * changed.
@@ -1744,7 +1755,7 @@
     }
 
     void getContentRect(Rect out) {
-        out.set(mContentRect);
+        out.set(mDisplayFrames.mContent);
     }
 
     TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) {
@@ -1847,8 +1858,8 @@
             mTmpRect2.setEmpty();
             for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
                 final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
-                stack.setTouchExcludeRegion(
-                        focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
+                stack.setTouchExcludeRegion(focusedTask, delta, mTouchExcludeRegion,
+                        mDisplayFrames.mContent, mTmpRect2);
             }
             // If we removed the focused task above, add it back and only leave its
             // outside touch area in the exclusion. TapDectector is not interested in
@@ -2025,7 +2036,7 @@
         final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
         final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
         final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock();
-        final int imeHeight = mService.mPolicy.getInputMethodWindowVisibleHeightLw();
+        final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight();
         final boolean imeHeightChanged = imeVisible &&
                 imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor();
 
@@ -2167,6 +2178,7 @@
         if (screenRotationAnimation != null) {
             screenRotationAnimation.writeToProto(proto, SCREEN_ROTATION_ANIMATION);
         }
+        mDisplayFrames.writeToProto(proto, DISPLAY_FRAMES);
         proto.end(token);
     }
 
@@ -2246,6 +2258,9 @@
             pw.println(subPrefix
                     + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
         }
+
+        pw.println();
+        mDisplayFrames.dump(prefix, pw);
     }
 
     @Override
@@ -2871,21 +2886,22 @@
 
         final int dw = mDisplayInfo.logicalWidth;
         final int dh = mDisplayInfo.logicalHeight;
-
         if (DEBUG_LAYOUT) {
             Slog.v(TAG, "-------------------------------------");
             Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
         }
 
-        mService.mPolicy.beginLayoutLw(mDisplayId, dw, dh, mRotation, getConfiguration().uiMode);
+        mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+        // TODO: Not sure if we really need to set the rotation here since we are updating from the
+        // display info above...
+        mDisplayFrames.mRotation = mRotation;
+        mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
         if (isDefaultDisplay) {
             // Not needed on non-default displays.
             mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
             mService.mScreenRect.set(0, 0, dw, dh);
         }
 
-        mService.mPolicy.getContentRectLw(mContentRect);
-
         int seq = mService.mLayoutSeq + 1;
         if (seq < 0) seq = 0;
         mService.mLayoutSeq = seq;
@@ -2915,7 +2931,6 @@
             mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
         }
 
-        mService.mPolicy.finishLayoutLw();
         mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
     }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 5ea0e1d..e693e5a 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -23,6 +23,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
@@ -116,6 +117,7 @@
     private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
+    private int mOriginalDockedSide = DOCKED_INVALID;
     private boolean mAnimatingForMinimizedDockedStack;
     private boolean mAnimationStarted;
     private long mAnimationStartTime;
@@ -408,6 +410,31 @@
         mDockedStackListeners.finishBroadcast();
     }
 
+    /**
+     * Checks if the primary stack is allowed to dock to a specific side based on its original dock
+     * side.
+     *
+     * @param dockSide the side to see if it is valid
+     * @return true if the side provided is valid
+     */
+    boolean canPrimaryStackDockTo(int dockSide) {
+        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+            // Side is the same as original side
+            if (dockSide == mOriginalDockedSide) {
+                return true;
+            }
+            // Special rule that the top in portrait is always valid
+            if (dockSide == DOCKED_TOP) {
+                return true;
+            }
+            // Only if original docked side was top in portrait will allow left side for landscape
+            if (dockSide == DOCKED_LEFT && mOriginalDockedSide == DOCKED_TOP) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void notifyDockedStackExistsChanged(boolean exists) {
         // TODO(multi-display): Perform all actions only for current display.
         final int size = mDockedStackListeners.beginBroadcast();
@@ -430,8 +457,11 @@
                 inputMethodManagerInternal.hideCurrentInputMethod();
                 mImeHideRequested = true;
             }
+            final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+            mOriginalDockedSide = stack.getDockSide();
             return;
         }
+        mOriginalDockedSide = DOCKED_INVALID;
         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bcb6e673..f541926 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -247,7 +247,7 @@
         if (mService.mDisplayManagerInternal != null) {
             mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
                     displayId, displayInfo);
-            mService.configureDisplayPolicyLocked(dc);
+            dc.configureDisplayPolicy();
 
             // Tap Listeners are supported for:
             // 1. All physical displays (multi-display).
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 170feac..dde7946 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -442,7 +442,7 @@
         mTmpRect2.set(mBounds);
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (inSplitScreenPrimaryWindowingMode()) {
-            repositionDockedStackAfterRotation(mTmpRect2);
+            repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
             snapDockedStackAfterRotation(mTmpRect2);
             final int newDockSide = getDockSide(mTmpRect2);
 
@@ -466,14 +466,14 @@
     }
 
     /**
-     * Some dock sides are not allowed by the policy. This method queries the policy and moves
-     * the docked stack around if needed.
+     * Some primary split screen sides are not allowed by the policy. This method queries the policy
+     * and moves the primary stack around if needed.
      *
-     * @param inOutBounds the bounds of the docked stack to adjust
+     * @param inOutBounds the bounds of the primary stack to adjust
      */
-    private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+    private void repositionPrimarySplitScreenStackAfterRotation(Rect inOutBounds) {
         int dockSide = getDockSide(inOutBounds);
-        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+        if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
             return;
         }
         mDisplayContent.getLogicalDisplayRect(mTmpRect);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 35870b1..df51be1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -245,6 +245,7 @@
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerService;
+import android.view.DisplayFrames;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
 
@@ -569,6 +570,8 @@
     boolean mForceResizableTasks = false;
     boolean mSupportsPictureInPicture = false;
 
+    private boolean mDisableTransitionAnimation = false;
+
     int getDragLayerLocked() {
         return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
     }
@@ -966,6 +969,8 @@
                 com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
         mMaxUiWidth = context.getResources().getInteger(
                 com.android.internal.R.integer.config_maxUiWidth);
+        mDisableTransitionAnimation = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_disableTransitionAnimation);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mDisplaySettings = new DisplaySettings();
@@ -1448,23 +1453,19 @@
                 prepareNoneTransitionForRelaunching(atoken);
             }
 
-            if (displayContent.isDefaultDisplay) {
-                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-                final Rect taskBounds;
-                if (atoken != null && atoken.getTask() != null) {
-                    taskBounds = mTmpRect;
-                    atoken.getTask().getBounds(mTmpRect);
-                } else {
-                    taskBounds = null;
-                }
-                if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayInfo.rotation,
-                        displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
-                        outStableInsets, outOutsets)) {
-                    res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
-                }
+            final DisplayFrames displayFrames = displayContent.mDisplayFrames;
+            // TODO: Not sure if onDisplayInfoUpdated() call is needed.
+            displayFrames.onDisplayInfoUpdated(displayContent.getDisplayInfo());
+            final Rect taskBounds;
+            if (atoken != null && atoken.getTask() != null) {
+                taskBounds = mTmpRect;
+                atoken.getTask().getBounds(mTmpRect);
             } else {
-                outContentInsets.setEmpty();
-                outStableInsets.setEmpty();
+                taskBounds = null;
+            }
+            if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayFrames, outContentInsets,
+                    outStableInsets, outOutsets)) {
+                res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
             }
 
             if (mInTouchMode) {
@@ -2279,6 +2280,14 @@
 
     boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
             int transit, boolean enter, boolean isVoiceInteraction) {
+        if (mDisableTransitionAnimation) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG_WM,
+                        "applyAnimation: transition animation is disabled. atoken=" + atoken);
+            }
+            atoken.mAppAnimator.clearAnimation();
+            return false;
+        }
         // Only apply an animation if the display isn't frozen.  If it is
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
@@ -4656,7 +4665,7 @@
         synchronized(mWindowMap) {
             mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TOUCHSCREEN);
-            configureDisplayPolicyLocked(getDefaultDisplayContentLocked());
+            getDefaultDisplayContentLocked().configureDisplayPolicy();
         }
 
         try {
@@ -5513,7 +5522,7 @@
         if (!mDisplayReady) {
             return;
         }
-        configureDisplayPolicyLocked(displayContent);
+        displayContent.configureDisplayPolicy();
         displayContent.setLayoutNeeded();
 
         final int displayId = displayContent.getDisplayId();
@@ -5534,18 +5543,6 @@
         mWindowPlacerLocked.performSurfacePlacement();
     }
 
-    void configureDisplayPolicyLocked(DisplayContent displayContent) {
-        mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
-                displayContent.mBaseDisplayWidth,
-                displayContent.mBaseDisplayHeight,
-                displayContent.mBaseDisplayDensity);
-
-        DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        mPolicy.setDisplayOverscan(displayContent.getDisplay(),
-                displayInfo.overscanLeft, displayInfo.overscanTop,
-                displayInfo.overscanRight, displayInfo.overscanBottom);
-    }
-
     /**
      * Get an array with display ids ordered by focus priority - last items should be given
      * focus first. Sparse array just maps position to displayId.
@@ -7358,7 +7355,9 @@
         @Override
         public int getInputMethodWindowVisibleHeight() {
             synchronized (mWindowMap) {
-                return mPolicy.getInputMethodWindowVisibleHeightLw();
+                // TODO(multi-display): Have caller pass in the display they are interested in.
+                final DisplayContent dc = getDefaultDisplayContentLocked();
+                return dc.mDisplayFrames.getInputMethodWindowVisibleHeight();
             }
         }
 
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 35e65bc..df85e31 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -17,18 +17,16 @@
 #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 4096
+#define MAX_DESCRIPTORS_LENGTH 16384
 
 // com.android.server.usb.descriptors
 extern "C" {
-jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors_1native(
+jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors(
         JNIEnv* env, jobject thiz, jstring deviceAddr) {
     const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL);
     struct usb_device* device = usb_device_open(deviceAddrStr);
@@ -41,7 +39,6 @@
 
     int fd = usb_device_get_fd(device);
     if (fd < 0) {
-        usb_device_close(device);
         return NULL;
     }
 
@@ -49,48 +46,17 @@
     jbyte buffer[MAX_DESCRIPTORS_LENGTH];
     lseek(fd, 0, SEEK_SET);
     int numBytes = read(fd, buffer, sizeof(buffer));
-    jbyteArray ret = NULL;
+
     usb_device_close(device);
 
-    if (numBytes > 0) {
+    jbyteArray ret = NULL;
+    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 24f2014..88ae824 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -22,6 +22,8 @@
 #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>
@@ -29,20 +31,22 @@
 #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_usbDeviceAdded;
+static jmethodID method_beginUsbDeviceAdded;
+static jmethodID method_addUsbConfiguration;
+static jmethodID method_addUsbInterface;
+static jmethodID method_addUsbEndpoint;
+static jmethodID method_endUsbDeviceAdded;
 static jmethodID method_usbDeviceRemoved;
 
 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
@@ -53,52 +57,101 @@
     }
 }
 
-static int usb_device_added(const char *devAddress, void* clientData) {
-    struct usb_device *device = usb_device_open(devAddress);
+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);
     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;
 
-    // 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);
+    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);
 
-        jbyteArray descriptorsArray = env->NewByteArray(numBytes);
-        const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device);
-        env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors);
+    jstring deviceName = env->NewStringUTF(devname);
+    jstring manufacturerName = AndroidRuntime::NewStringLatin1(env, manufacturer);
+    jstring productName = AndroidRuntime::NewStringLatin1(env, product);
+    jstring serialNumber = AndroidRuntime::NewStringLatin1(env, serial);
 
-        env->CallBooleanMethod(thiz, method_usbDeviceAdded,
-                deviceAddress, classID, subClassID, descriptorsArray);
+    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->DeleteLocalRef(descriptorsArray);
-        env->DeleteLocalRef(deviceAddress);
+    env->DeleteLocalRef(serialNumber);
+    env->DeleteLocalRef(productName);
+    env->DeleteLocalRef(manufacturerName);
+    env->DeleteLocalRef(deviceName);
+    free(manufacturer);
+    free(product);
+    free(serial);
 
-        checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    } else {
-        // TODO return an error code here?
-        ALOGE("error reading descriptors\n");
+    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);
+        }
     }
 
+    env->CallVoidMethod(thiz, method_endUsbDeviceAdded);
+
+fail:
     usb_device_close(device);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
 
     return 0;
 }
 
-static int usb_device_removed(const char *devAddress, void* clientData) {
+static int usb_device_removed(const char *devname, void* client_data) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jobject thiz = (jobject)clientData;
+    jobject thiz = (jobject)client_data;
 
-    jstring deviceAddress = env->NewStringUTF(devAddress);
-    env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceAddress);
-    env->DeleteLocalRef(deviceAddress);
+    jstring deviceName = env->NewStringUTF(devname);
+    env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceName);
+    env->DeleteLocalRef(deviceName);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return 0;
 }
@@ -115,11 +168,11 @@
 }
 
 static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */,
-                                                        jstring deviceAddress)
+                                                        jstring deviceName)
 {
-    const char *deviceAddressStr = env->GetStringUTFChars(deviceAddress, NULL);
-    struct usb_device* device = usb_device_open(deviceAddressStr);
-    env->ReleaseStringUTFChars(deviceAddress, deviceAddressStr);
+    const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);
+    struct usb_device* device = usb_device_open(deviceNameStr);
+    env->ReleaseStringUTFChars(deviceName, deviceNameStr);
 
     if (!device)
         return NULL;
@@ -153,12 +206,34 @@
         ALOGE("Can't find com/android/server/usb/UsbHostManager");
         return -1;
     }
-    method_usbDeviceAdded =
-            env->GetMethodID(clazz, "usbDeviceAdded", "(Ljava/lang/String;II[B)Z");
-    if (method_usbDeviceAdded == NULL) {
+    method_beginUsbDeviceAdded = env->GetMethodID(clazz, "beginUsbDeviceAdded",
+            "(Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;ILjava/lang/String;)Z");
+    if (method_beginUsbDeviceAdded == 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/robotests/Android.mk b/services/robotests/Android.mk
index 322b891..59d2a24 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -26,13 +26,8 @@
 LOCAL_PRIVILEGED_MODULE := true
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    frameworks-base-testutils \
     services.backup \
-    services.core \
-    android-support-test \
-    mockito-target-minus-junit4 \
-    platform-test-annotations \
-    truth-prebuilt
+    services.core
 
 include $(BUILD_PACKAGE)
 
@@ -45,6 +40,9 @@
 
 # Include the testing libraries (JUnit4 + Robolectric libs).
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-robolectric-prebuilt \
+    platform-test-annotations \
     truth-prebuilt
 
 LOCAL_JAVA_LIBRARIES := \
diff --git a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
index cbe9650..8ac6481 100644
--- a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,7 +17,7 @@
 package com.android.server.notification;
 
 import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -100,5 +100,34 @@
                 AudioAttributes.USAGE_GAME);
         verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
                 AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_UNKNOWN);
+    }
+
+    @Test
+    public void testZenAllCannotBypass() {
+        // Only audio attributes with SUPPRESIBLE_NEVER can bypass
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mConfig.allowAlarms = false;
+        mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+        mZenModeHelperSpy.mConfig.allowReminders = false;
+        mZenModeHelperSpy.mConfig.allowCalls = false;
+        mZenModeHelperSpy.mConfig.allowMessages = false;
+        mZenModeHelperSpy.mConfig.allowEvents = false;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+        assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+        assertFalse(mZenModeHelperSpy.mConfig.allowReminders);
+        assertFalse(mZenModeHelperSpy.mConfig.allowCalls);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMessages);
+        assertFalse(mZenModeHelperSpy.mConfig.allowEvents);
+        assertFalse(mZenModeHelperSpy.mConfig.allowRepeatCallers);
+        mZenModeHelperSpy.applyRestrictions();
+
+        for (int usage : AudioAttributes.SDK_USAGES) {
+            boolean shouldMute = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage)
+                    != AudioAttributes.SUPPRESSIBLE_NEVER;
+            verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(shouldMute, usage);
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 31ed8ba..bf912dd 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -1,50 +1,93 @@
 package com.android.server.job;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
-import android.content.ComponentName;
-import android.content.Context;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.job.JobInfo;
 import android.app.job.JobInfo.Builder;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkRequest;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.SystemClock;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.RenamingDelegatingContext;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.util.HexDump;
+import com.android.server.IoThread;
 import com.android.server.job.JobStore.JobSet;
 import com.android.server.job.controllers.JobStatus;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.Arrays;
 import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test reading and writing correctly from file.
  */
-public class JobStoreTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class JobStoreTest {
     private static final String TAG = "TaskStoreTest";
     private static final String TEST_PREFIX = "_test_";
 
-    private static final int SOME_UID = 34234;
+    private static final int SOME_UID = android.os.Process.FIRST_APPLICATION_UID;
     private ComponentName mComponent;
-    private static final long IO_WAIT = 1000L;
 
     JobStore mTaskStoreUnderTest;
     Context mTestContext;
 
-    @Override
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    @Before
     public void setUp() throws Exception {
         mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);
         Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'");
         mTaskStoreUnderTest =
                 JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir());
         mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
+
+        // Freeze the clocks at this moment in time
+        JobSchedulerService.sSystemClock =
+                Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+        JobSchedulerService.sUptimeMillisClock =
+                Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         mTaskStoreUnderTest.clear();
     }
 
+    private void waitForPendingIo() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        IoThread.getHandler().post(() -> {
+            latch.countDown();
+        });
+        latch.await(10, TimeUnit.SECONDS);
+    }
+
+    @Test
     public void testMaybeWriteStatusToDisk() throws Exception {
         int taskId = 5;
         long runByMillis = 20000L; // 20s
@@ -61,7 +104,8 @@
                 .build();
         final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
         mTaskStoreUnderTest.add(ts);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
+
         // Manually load tasks from xml file.
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -75,9 +119,9 @@
                 ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
         compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
                 ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
-
     }
 
+    @Test
     public void testWritingTwoFilesToDisk() throws Exception {
         final JobInfo task1 = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
@@ -96,7 +140,7 @@
         final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null);
         mTaskStoreUnderTest.add(taskStatus1);
         mTaskStoreUnderTest.add(taskStatus2);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -125,9 +169,9 @@
                 taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
         compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
                 taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
-
     }
 
+    @Test
     public void testWritingTaskWithExtras() throws Exception {
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
@@ -144,7 +188,7 @@
         JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
 
         mTaskStoreUnderTest.add(taskStatus);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -152,6 +196,8 @@
         JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
         assertTasksEqual(task, loaded.getJob());
     }
+
+    @Test
     public void testWritingTaskWithSourcePackage() throws Exception {
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
@@ -162,7 +208,7 @@
                 "com.google.android.gms", 0, null);
 
         mTaskStoreUnderTest.add(taskStatus);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -174,6 +220,7 @@
                 taskStatus.getSourceUserId());
     }
 
+    @Test
     public void testWritingTaskWithFlex() throws Exception {
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
@@ -183,7 +230,7 @@
         JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
 
         mTaskStoreUnderTest.add(taskStatus);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -195,6 +242,7 @@
                 taskStatus.getJob().getFlexMillis());
     }
 
+    @Test
     public void testMassivePeriodClampedOnRead() throws Exception {
         final long ONE_HOUR = 60*60*1000L; // flex
         final long TWO_HOURS = 2 * ONE_HOUR; // period
@@ -214,7 +262,7 @@
                 persistedExecutionTimesUTC);
 
         mTaskStoreUnderTest.add(js);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -231,6 +279,7 @@
                 loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS + ONE_HOUR);
     }
 
+    @Test
     public void testPriorityPersisted() throws Exception {
         JobInfo.Builder b = new Builder(92, mComponent)
                 .setOverrideDeadline(5000)
@@ -238,7 +287,8 @@
                 .setPersisted(true);
         final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
         mTaskStoreUnderTest.add(js);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
+
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
@@ -248,6 +298,7 @@
     /**
      * Test that non persisted job is not written to disk.
      */
+    @Test
     public void testNonPersistedTaskIsNotPersisted() throws Exception {
         JobInfo.Builder b = new Builder(42, mComponent)
                 .setOverrideDeadline(10000)
@@ -259,7 +310,8 @@
                 .setPersisted(true);
         JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
         mTaskStoreUnderTest.add(jsPersisted);
-        Thread.sleep(IO_WAIT);
+        waitForPendingIo();
+
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         assertEquals("Job count is incorrect.", 1, jobStatusSet.size());
@@ -267,6 +319,62 @@
         assertEquals("Wrong job persisted.", 43, jobStatus.getJobId());
     }
 
+    @Test
+    public void testRequiredNetworkType() throws Exception {
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiresDeviceIdle(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE).build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED).build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING).build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR).build());
+    }
+
+    @Test
+    public void testRequiredNetwork() throws Exception {
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiresDeviceIdle(true)
+                .setRequiredNetwork(null).build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder().build()).build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder()
+                        .addTransportType(TRANSPORT_WIFI).build())
+                .build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_IMS).build())
+                .build());
+    }
+
+    /**
+     * Helper function to kick a {@link JobInfo} through a persistence cycle and
+     * assert that it's unchanged.
+     */
+    private void assertPersistedEquals(JobInfo first) throws Exception {
+        mTaskStoreUnderTest.clear();
+        mTaskStoreUnderTest.add(JobStatus.createFromJobInfo(first, SOME_UID, null, -1, null));
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
+        assertTasksEqual(first, second.getJob());
+    }
+
     /**
      * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
      */
@@ -286,12 +394,10 @@
                 second.isRequireBatteryNotLow());
         assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
                 second.isRequireDeviceIdle());
-        assertEquals("Invalid unmetered constraint.",
-                first.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED,
-                second.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED);
-        assertEquals("Invalid connectivity constraint.",
-                first.getNetworkType() == JobInfo.NETWORK_TYPE_ANY,
-                second.getNetworkType() == JobInfo.NETWORK_TYPE_ANY);
+        assertEquals("Invalid network type.",
+                first.getNetworkType(), second.getNetworkType());
+        assertEquals("Invalid network.",
+                first.getRequiredNetwork(), second.getRequiredNetwork());
         assertEquals("Invalid deadline constraint.",
                 first.hasLateConstraint(),
                 second.hasLateConstraint());
@@ -302,6 +408,26 @@
                 first.getExtras().toString(), second.getExtras().toString());
         assertEquals("Transient xtras don't match",
                 first.getTransientExtras().toString(), second.getTransientExtras().toString());
+
+        // Since people can forget to add tests here for new fields, do one last
+        // sanity check based on bits-on-wire equality.
+        final byte[] firstBytes = marshall(first);
+        final byte[] secondBytes = marshall(second);
+        if (!Arrays.equals(firstBytes, secondBytes)) {
+            Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
+            Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
+            fail("Raw JobInfo aren't equal; see logs for details");
+        }
+    }
+
+    private static byte[] marshall(Parcelable p) {
+        final Parcel parcel = Parcel.obtain();
+        try {
+            p.writeToParcel(parcel, 0);
+            return parcel.marshall();
+        } finally {
+            parcel.recycle();
+        }
     }
 
     /**
@@ -312,7 +438,7 @@
      */
     private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) {
         final long DELTA_MILLIS = 700L;  // We allow up to 700ms of latency for IO read/writes.
-        assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT);
+        assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS);
     }
 
     private static class StubClass {}
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
index 9c80544..f23bd62 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -16,8 +16,12 @@
 
 package com.android.server.wm;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.graphics.Color.BLUE;
 import static android.graphics.Color.RED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.LEFT;
 import static android.view.Gravity.RIGHT;
@@ -33,16 +37,23 @@
 import static org.junit.Assert.assertEquals;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
 import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
-import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -51,7 +62,6 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -65,7 +75,6 @@
  */
 // TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags.
 // TODO: Test non-Activity windows.
-// TODO: Test secondary display.
 @SmallTest
 // TODO(b/68957554)
 //@Presubmit
@@ -78,22 +87,26 @@
     private WindowManager mWm;
     private ArrayList<View> mWindows = new ArrayList<>();
 
-    @Rule
-    public ActivityTestRule<TestActivity> mTestActivityRule = new ActivityTestRule<>(
-            TestActivity.class, false /* initialTouchMode */, false /* launchActivity */);
     private Activity mTestActivity;
+    private VirtualDisplay mDisplay;
+    private ImageReader mImageReader;
 
     private int mDecorThickness;
     private int mHalfDecorThickness;
 
     @Before
     public void setUp() {
-        mWm = mContext.getSystemService(WindowManager.class);
+        final Pair<VirtualDisplay, ImageReader> result = createDisplay();
+        mDisplay = result.first;
+        mImageReader = result.second;
+        final Display display = mDisplay.getDisplay();
+        final Context dContext = mContext.createDisplayContext(display);
+        mWm = dContext.getSystemService(WindowManager.class);
+        mTestActivity = startActivityOnDisplay(TestActivity.class, display.getDisplayId());
         final Point size = new Point();
-        mWm.getDefaultDisplay().getSize(size);
+        mDisplay.getDisplay().getRealSize(size);
         mDecorThickness = Math.min(size.x, size.y) / 3;
         mHalfDecorThickness = mDecorThickness / 2;
-        mTestActivity = launchActivity(mTestActivityRule);
     }
 
     @After
@@ -101,7 +114,9 @@
         while (!mWindows.isEmpty()) {
             removeWindow(mWindows.get(0));
         }
-        finishActivity(mTestActivityRule);
+        finishActivity(mTestActivity);
+        mDisplay.release();
+        mImageReader.close();
     }
 
     @Test
@@ -259,23 +274,48 @@
         Assert.assertTrue("Excepted " + first + " >= " + second, first >= second);
     }
 
-    private Activity launchActivity(ActivityTestRule activityRule) {
-        final Activity activity = activityRule.launchActivity(null);
-        waitForIdle();
-        return activity;
-    }
-
-    private void finishActivity(ActivityTestRule activityRule) {
-        final Activity activity = activityRule.getActivity();
-        if (activity != null) {
-            activity.finish();
+    private void finishActivity(Activity a) {
+        if (a == null) {
+            return;
         }
+        a.finish();
+        waitForIdle();
     }
 
     private void waitForIdle() {
         mInstrumentation.waitForIdleSync();
     }
 
+    private Activity startActivityOnDisplay(Class<?> cls, int displayId) {
+        final Intent intent = new Intent(mContext, cls);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(displayId);
+        final Activity activity = mInstrumentation.startActivitySync(intent, options.toBundle());
+        waitForIdle();
+
+        assertEquals(displayId, activity.getDisplay().getDisplayId());
+        return activity;
+    }
+
+    private Pair<VirtualDisplay, ImageReader> createDisplay() {
+        final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        final DisplayInfo displayInfo = new DisplayInfo();
+        final Display defaultDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+        defaultDisplay.getDisplayInfo(displayInfo);
+        final String name = "ScreenDecorWindowTests";
+        int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+
+        final ImageReader imageReader = ImageReader.newInstance(
+                displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);
+
+        final VirtualDisplay display = dm.createVirtualDisplay(name, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, displayInfo.logicalDensityDpi, imageReader.getSurface(),
+                flags);
+
+        return Pair.create(display, imageReader);
+    }
+
     public static class TestActivity extends Activity {
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 5134c26..53d0bfb 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -133,11 +133,6 @@
     }
 
     @Override
-    public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
-
-    }
-
-    @Override
     public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
         return 0;
     }
@@ -290,40 +285,11 @@
     }
 
     @Override
-    public void beginLayoutLw(int displayId, int displayWidth, int displayHeight,
-            int displayRotation, int uiMode) {
-
-    }
-
-    @Override
     public int getSystemDecorLayerLw() {
         return 0;
     }
 
     @Override
-    public void getContentRectLw(Rect r) {
-
-    }
-
-    @Override
-    public void layoutWindowLw(WindowState win,
-            WindowState attached) {
-
-    }
-
-    @Override
-    public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
-            Rect outStableInsets, Rect outOutsets) {
-        return false;
-    }
-
-    @Override
-    public void finishLayoutLw() {
-
-    }
-
-    @Override
     public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) {
 
     }
@@ -582,11 +548,6 @@
     }
 
     @Override
-    public int getInputMethodWindowVisibleHeightLw() {
-        return 0;
-    }
-
-    @Override
     public void setCurrentUserLw(int newUserId) {
 
     }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 7bea8a1..d359b70 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -255,7 +255,6 @@
     }
 
     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 c29a335..095fdc6 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -19,8 +19,13 @@
 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;
@@ -32,6 +37,7 @@
 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;
 
@@ -44,23 +50,28 @@
 
     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;
 
-    private Object mHandlerLock = new Object();
-    @GuardedBy("mHandlerLock")
+    @GuardedBy("mLock")
     private ComponentName mUsbDeviceConnectionHandler;
 
     public UsbHostManager(Context context, UsbAlsaManager alsaManager,
@@ -80,33 +91,33 @@
     }
 
     public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
-        synchronized (mSettingsLock) {
+        synchronized (mLock) {
             mCurrentSettings = settings;
         }
     }
 
     private UsbProfileGroupSettingsManager getCurrentUserSettings() {
-        synchronized (mSettingsLock) {
+        synchronized (mLock) {
             return mCurrentSettings;
         }
     }
 
     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
-        synchronized (mHandlerLock) {
+        synchronized (mLock) {
             mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
         }
     }
 
     private @Nullable ComponentName getUsbDeviceConnectionHandler() {
-        synchronized (mHandlerLock) {
+        synchronized (mLock) {
             return mUsbDeviceConnectionHandler;
         }
     }
 
-    private boolean isBlackListed(String deviceAddress) {
+    private boolean isBlackListed(String deviceName) {
         int count = mHostBlacklist.length;
         for (int i = 0; i < count; i++) {
-            if (deviceAddress.startsWith(mHostBlacklist[i])) {
+            if (deviceName.startsWith(mHostBlacklist[i])) {
                 return true;
             }
         }
@@ -125,73 +136,166 @@
     }
 
     /* Called from JNI in monitorUsbHostBus() to report new USB devices
-       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.
+       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
      */
     @SuppressWarnings("unused")
-    private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
-            byte[] descriptors) {
+    private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
+            int deviceClass, int deviceSubclass, int deviceProtocol,
+            String manufacturerName, String productName, int version, String serialNumber) {
+
         if (DEBUG) {
-            Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
+            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);
         }
 
-        // 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");
-            }
+        // 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)) {
             return false;
         }
 
         synchronized (mLock) {
-            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.
+            if (mDevices.get(deviceName) != null) {
+                Slog.w(TAG, "device already on mDevices list: " + deviceName);
                 return false;
             }
 
-            UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress);
-            if (parser.parseDescriptors(descriptors)) {
-
-                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(newDevice);
-                } else {
-                    getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
-                            usbDeviceConnectionHandler);
-                }
-
-                // 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, "Error parsing USB device descriptors for " + deviceAddress);
+            if (mNewDevice != null) {
+                Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
                 return false;
             }
-        }
 
-        if (DEBUG) {
-            Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
+            // 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);
+
+                // 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);
+                } else {
+                    getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
+                            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);
+            } else {
+                Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
+            }
+            mNewDevice = null;
+            mNewConfigurations = null;
+            mNewInterfaces = null;
+            mNewEndpoints = null;
+            mNewConfiguration = null;
+            mNewInterface = null;
+        }
+    }
+
     /* Called from JNI in monitorUsbHostBus to report USB device removal */
     @SuppressWarnings("unused")
-    private void usbDeviceRemoved(String deviceAddress) {
+    private void usbDeviceRemoved(String deviceName) {
         synchronized (mLock) {
-            UsbDevice device = mDevices.remove(deviceAddress);
+            UsbDevice device = mDevices.remove(deviceName);
             if (device != null) {
                 mUsbAlsaManager.usbDeviceRemoved(device);
                 mSettingsManager.usbDeviceRemoved(device);
@@ -218,37 +322,32 @@
         }
     }
 
-    /**
-     * Opens the specified USB device
-     * @hide
-     */
-    public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings) {
+    /* Opens the specified USB device */
+    public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
         synchronized (mLock) {
-            if (isBlackListed(deviceAddress)) {
+            if (isBlackListed(deviceName)) {
                 throw new SecurityException("USB device is on a restricted bus");
             }
-            UsbDevice device = mDevices.get(deviceAddress);
+            UsbDevice device = mDevices.get(deviceName);
             if (device == null) {
                 // if it is not in mDevices, it either does not exist or is blacklisted
                 throw new IllegalArgumentException(
-                        "device " + deviceAddress + " does not exist or is restricted");
+                        "device " + deviceName + " does not exist or is restricted");
             }
             settings.checkPermission(device);
-            return nativeOpenDevice(deviceAddress);
+            return nativeOpenDevice(deviceName);
         }
     }
 
     public void dump(IndentingPrintWriter pw) {
-        pw.println("USB Host State:");
-        synchronized (mHandlerLock) {
-            if (mUsbDeviceConnectionHandler != null) {
-                pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
-            }
-        }
         synchronized (mLock) {
+            pw.println("USB Host State:");
             for (String name : mDevices.keySet()) {
                 pw.println("  " + name + ": " + mDevices.get(name));
             }
+            if (mUsbDeviceConnectionHandler != null) {
+                pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
+            }
 
             Collection<UsbDevice> devices = mDevices.values();
             if (devices.size() != 0) {
@@ -256,12 +355,17 @@
                 for (UsbDevice device : devices) {
                     StringBuilder stringBuilder = new StringBuilder();
 
-                    UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName());
-                    if (parser.parseDevice()) {
+                    UsbDescriptorParser parser = new UsbDescriptorParser();
+                    if (parser.parseDevice(device.getDeviceName())) {
                         UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
                         descriptorTree.parse(parser);
 
-                        descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
+                        UsbManager usbManager =
+                                (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
+                        UsbDeviceConnection connection = usbManager.openDevice(device);
+
+                        descriptorTree.report(new TextReportCanvas(connection, stringBuilder));
+                        connection.close();
 
                         stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
                                 + " , out: " + parser.isOutputHeadset() + "]");
@@ -277,5 +381,5 @@
     }
 
     private native void monitorUsbHostBus();
-    private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
+    private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
 }
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 511e59a..75279c6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -15,13 +15,8 @@
  */
 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.
@@ -40,9 +35,6 @@
                                 //     D4..0 Reserved, set to 0.
     private byte mMaxPower;     // 8:1 Maximum Power Consumption in 2mA units
 
-    private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
-            new ArrayList<UsbInterfaceDescriptor>();
-
     UsbConfigDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 2;
@@ -72,22 +64,6 @@
         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();
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 c5052c8..ad7bde5c 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -15,7 +15,6 @@
  */
 package com.android.server.usb.descriptors;
 
-import android.hardware.usb.UsbDevice;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -26,16 +25,11 @@
  */
 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
@@ -43,13 +37,7 @@
     // Obtained from the first AudioClass Header descriptor.
     private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
 
-    public UsbDescriptorParser(String deviceAddr) {
-        mDeviceAddr = deviceAddr;
-    }
-
-    public String getDeviceAddr() {
-        return mDeviceAddr;
-    }
+    public UsbDescriptorParser() {}
 
     /**
      * @return the USB Spec value associated with the Device descriptor for the
@@ -72,18 +60,6 @@
     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.
@@ -91,8 +67,7 @@
     private static final float IN_HEADSET_TRIGGER = 0.75f;
     private static final float OUT_HEADSET_TRIGGER = 0.75f;
 
-    private UsbDescriptor allocDescriptor(ByteStream stream)
-            throws UsbDescriptorsStreamFormatException {
+    private UsbDescriptor allocDescriptor(ByteStream stream) {
         stream.resetReadCount();
 
         int length = stream.getUnsignedByte();
@@ -108,38 +83,15 @@
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
-                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!");
-                }
+                descriptor = new UsbConfigDescriptor(length, type);
                 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;
 
             /*
@@ -192,12 +144,8 @@
     /**
      * @hide
      */
-    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);
+    public void parseDescriptors(byte[] descriptors) {
+        mDescriptors.clear();
 
         ByteStream stream = new ByteStream(descriptors);
         while (stream.available() > 0) {
@@ -225,36 +173,21 @@
                 }
             }
         }
-        if (DEBUG) {
-            Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
+    }
+
+    /**
+     * @hide
+     */
+    public boolean parseDevice(String deviceAddr) {
+        byte[] rawDescriptors = getRawDescriptors(deviceAddr);
+        if (rawDescriptors != null) {
+            parseDescriptors(rawDescriptors);
+            return true;
         }
-        return true;
+        return false;
     }
 
-    /**
-     * @hide
-     */
-    public boolean parseDevice() {
-        byte[] rawDescriptors = getRawDescriptors();
-
-        return rawDescriptors != null
-            ? parseDescriptors(rawDescriptors) : false;
-    }
-
-    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);
+    private native byte[] getRawDescriptors(String deviceAddr);
 
     public int getParsingSpec() {
         return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
@@ -267,17 +200,6 @@
     /**
      * @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) {
@@ -433,6 +355,8 @@
      * to count on the peripheral being a headset.
      */
     public boolean isInputHeadset() {
+        // TEMP
+        Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%");
         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
     }
 
@@ -486,6 +410,8 @@
      * 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 c89d9b6..d5cb89e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -15,14 +15,9 @@
  */
 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.
@@ -49,9 +44,6 @@
     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;
@@ -105,35 +97,6 @@
         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();
@@ -171,11 +134,12 @@
                 + " Product ID: " + ReportCanvas.getHexString(getProductID())
                 + " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));
 
-        UsbDescriptorParser parser = canvas.getParser();
         byte mfgIndex = getMfgIndex();
-        String manufacturer = parser.getDescriptorString(mfgIndex);
+        String manufacturer =
+                UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex);
         byte productIndex = getProductIndex();
-        String product = parser.getDescriptorString(productIndex);
+        String product =
+                UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), 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 9cb8f05..6322fbe 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.usb.descriptors;
 
-import android.hardware.usb.UsbEndpoint;
-
 import com.android.server.usb.descriptors.report.ReportCanvas;
 
 /**
@@ -107,10 +105,6 @@
         return mSyncAddress;
     }
 
-    /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
-        return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
-    }
-
     @Override
     public int parseRawDescriptors(ByteStream stream) {
         mEndpointAddress = stream.getByte();
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 645807d..4eef6ca 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -15,14 +15,9 @@
  */
 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.
@@ -39,9 +34,6 @@
     protected byte 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;
@@ -88,22 +80,6 @@
         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);
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 adfc514..99ebcca 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 com.android.server.usb.descriptors.UsbDescriptorParser;
+import android.hardware.usb.UsbDeviceConnection;
 
 /**
  * @hide
@@ -32,8 +32,8 @@
      * from the USB device.
      * @param stringBuilder Generated output gets written into this object.
      */
-    public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
-        super(parser);
+    public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
+        super(connection);
 
         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 c34dc98..9e0adf5 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 com.android.server.usb.descriptors.UsbDescriptorParser;
+import android.hardware.usb.UsbDeviceConnection;
 
 /**
  * @hide
@@ -24,19 +24,22 @@
 public abstract class ReportCanvas {
     private static final String TAG = "ReportCanvas";
 
-    private final UsbDescriptorParser mParser;
+    private final UsbDeviceConnection mConnection;
 
     /**
      * Constructor.
      * @param connection    The USB connection object used to retrieve strings
      * from the USB device.
      */
-    public ReportCanvas(UsbDescriptorParser parser) {
-        mParser = parser;
+    public ReportCanvas(UsbDeviceConnection connection) {
+        mConnection = connection;
     }
 
-    public UsbDescriptorParser getParser() {
-        return mParser;
+    /**
+     * @returns the UsbDeviceConnection member (mConnection).
+     */
+    public UsbDeviceConnection getConnection() {
+        return mConnection;
     }
 
     /**
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 1e19ea1..a43569d 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 com.android.server.usb.descriptors.UsbDescriptorParser;
+import android.hardware.usb.UsbDeviceConnection;
 
 /**
  * @hide
@@ -34,8 +34,8 @@
      * from the USB device.
      * @param stringBuilder Generated output gets written into this object.
      */
-    public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
-        super(parser);
+    public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
+        super(connection);
 
         mStringBuilder = stringBuilder;
     }
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index f601d8b..4b827d2 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -16,16 +16,35 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  *  Encapsulates the telecom audio state, including the current audio routing, supported audio
  *  routing and mute.
  */
 public final class CallAudioState implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+            flag=true)
+    public @interface CallAudioRoute {}
+
     /** Direct the audio stream through the device's earpiece. */
     public static final int ROUTE_EARPIECE      = 0x00000001;
 
@@ -55,6 +74,8 @@
     private final boolean isMuted;
     private final int route;
     private final int supportedRouteMask;
+    private final BluetoothDevice activeBluetoothDevice;
+    private final Collection<BluetoothDevice> supportedBluetoothDevices;
 
     /**
      * Constructor for a {@link CallAudioState} object.
@@ -73,10 +94,21 @@
      * {@link #ROUTE_WIRED_HEADSET}
      * {@link #ROUTE_SPEAKER}
      */
-    public CallAudioState(boolean muted, int route, int supportedRouteMask) {
-        this.isMuted = muted;
+    public CallAudioState(boolean muted, @CallAudioRoute int route,
+            @CallAudioRoute int supportedRouteMask) {
+        this(muted, route, supportedRouteMask, null, Collections.emptyList());
+    }
+
+    /** @hide */
+    public CallAudioState(boolean isMuted, @CallAudioRoute int route,
+            @CallAudioRoute int supportedRouteMask,
+            @Nullable BluetoothDevice activeBluetoothDevice,
+            @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
+        this.isMuted = isMuted;
         this.route = route;
         this.supportedRouteMask = supportedRouteMask;
+        this.activeBluetoothDevice = activeBluetoothDevice;
+        this.supportedBluetoothDevices = supportedBluetoothDevices;
     }
 
     /** @hide */
@@ -84,6 +116,8 @@
         isMuted = state.isMuted();
         route = state.getRoute();
         supportedRouteMask = state.getSupportedRouteMask();
+        activeBluetoothDevice = state.activeBluetoothDevice;
+        supportedBluetoothDevices = state.getSupportedBluetoothDevices();
     }
 
     /** @hide */
@@ -92,6 +126,8 @@
         isMuted = state.isMuted();
         route = state.getRoute();
         supportedRouteMask = state.getSupportedRouteMask();
+        activeBluetoothDevice = null;
+        supportedBluetoothDevices = Collections.emptyList();
     }
 
     @Override
@@ -103,17 +139,32 @@
             return false;
         }
         CallAudioState state = (CallAudioState) obj;
-        return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
-                getSupportedRouteMask() == state.getSupportedRouteMask();
+        if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
+            return false;
+        }
+        for (BluetoothDevice device : supportedBluetoothDevices) {
+            if (!state.supportedBluetoothDevices.contains(device)) {
+                return false;
+            }
+        }
+        return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
+                state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
+                state.getSupportedRouteMask();
     }
 
     @Override
     public String toString() {
+        String bluetoothDeviceList = supportedBluetoothDevices.stream()
+                .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+
         return String.format(Locale.US,
-                "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+                "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
+                        "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
                 isMuted,
                 audioRouteToString(route),
-                audioRouteToString(supportedRouteMask));
+                audioRouteToString(supportedRouteMask),
+                activeBluetoothDevice,
+                bluetoothDeviceList);
     }
 
     /**
@@ -126,6 +177,7 @@
     /**
      * @return The current audio route being used.
      */
+    @CallAudioRoute
     public int getRoute() {
         return route;
     }
@@ -133,11 +185,27 @@
     /**
      * @return Bit mask of all routes supported by this call.
      */
+    @CallAudioRoute
     public int getSupportedRouteMask() {
         return supportedRouteMask;
     }
 
     /**
+     * @return The {@link BluetoothDevice} through which audio is being routed.
+     *         Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
+     */
+    public BluetoothDevice getActiveBluetoothDevice() {
+        return activeBluetoothDevice;
+    }
+
+    /**
+     * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
+     */
+    public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
+        return supportedBluetoothDevices;
+    }
+
+    /**
      * Converts the provided audio route into a human readable string representation.
      *
      * @param route to convert into a string.
@@ -177,7 +245,13 @@
             boolean isMuted = source.readByte() == 0 ? false : true;
             int route = source.readInt();
             int supportedRouteMask = source.readInt();
-            return new CallAudioState(isMuted, route, supportedRouteMask);
+            BluetoothDevice activeBluetoothDevice = source.readParcelable(
+                    ClassLoader.getSystemClassLoader());
+            List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
+            source.readParcelableList(supportedBluetoothDevices,
+                    ClassLoader.getSystemClassLoader());
+            return new CallAudioState(isMuted, route,
+                    supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
         }
 
         @Override
@@ -202,6 +276,8 @@
         destination.writeByte((byte) (isMuted ? 1 : 0));
         destination.writeInt(route);
         destination.writeInt(supportedRouteMask);
+        destination.writeParcelable(activeBluetoothDevice, 0);
+        destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
     }
 
     private static void listAppend(StringBuffer buffer, String str) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8ba934c..ffb5e93 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -819,7 +820,7 @@
         public void onConnectionEvent(Connection c, String event, Bundle extras) {}
         /** @hide */
         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
-        public void onAudioRouteChanged(Connection c, int audioRoute) {}
+        public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {}
         public void onRttInitiationSuccess(Connection c) {}
         public void onRttInitiationFailure(Connection c, int reason) {}
         public void onRttSessionRemotelyTerminated(Connection c) {}
@@ -2576,7 +2577,29 @@
      */
     public final void setAudioRoute(int route) {
         for (Listener l : mListeners) {
-            l.onAudioRouteChanged(this, route);
+            l.onAudioRouteChanged(this, route, null);
+        }
+    }
+
+    /**
+     *
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified if the
+     * bluetooth stack is unable to route audio to the requested device.
+     * A list of available devices can be obtained via
+     * {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * <p>
+     * Used by self-managed {@link ConnectionService}s which wish to use bluetooth audio for a
+     * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+     * <p>
+     * See also {@link InCallService#requestBluetoothAudio(String)}
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     *                         {@link BluetoothDevice#getAddress()}.
+     */
+    public void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+        for (Listener l : mListeners) {
+            l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
         }
     }
 
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index a81fba9..35804f6 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1294,10 +1294,10 @@
         }
 
         @Override
-        public void onAudioRouteChanged(Connection c, int audioRoute) {
+        public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
             String id = mIdByConnection.get(c);
             if (id != null) {
-                mAdapter.setAudioRoute(id, audioRoute);
+                mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
             }
         }
 
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 111fcc7..92a9dc2 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -520,11 +520,14 @@
      * @param callId The unique ID of the call.
      * @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
      */
-    void setAudioRoute(String callId, int audioRoute) {
-        Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+    void setAudioRoute(String callId, int audioRoute, String bluetoothAddress) {
+        Log.v(this, "setAudioRoute: %s %s %s", callId,
+                CallAudioState.audioRouteToString(audioRoute),
+                bluetoothAddress);
         for (IConnectionServiceAdapter adapter : mAdapters) {
             try {
-                adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+                adapter.setAudioRoute(callId, audioRoute,
+                        bluetoothAddress, Log.getExternalSession());
             } catch (RemoteException ignored) {
             }
         }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index b1617f4..3fbdeb1 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -298,8 +298,8 @@
                 case MSG_SET_AUDIO_ROUTE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        mDelegate.setAudioRoute((String) args.arg1, args.argi1,
-                                (Session.Info) args.arg2);
+                        mDelegate.setAudioRoute((String) args.arg1, args.argi1, (String) args.arg2,
+                                (Session.Info) args.arg3);
                     } finally {
                         args.recycle();
                     }
@@ -548,12 +548,12 @@
 
         @Override
         public final void setAudioRoute(String connectionId, int audioRoute,
-                Session.Info sessionInfo) {
-
+                String bluetoothAddress, Session.Info sessionInfo) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = connectionId;
             args.argi1 = audioRoute;
-            args.arg2 = sessionInfo;
+            args.arg2 = bluetoothAddress;
+            args.arg3 = sessionInfo;
             mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
         }
 
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 9559a28..9bf0467 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
 import android.os.RemoteException;
 
@@ -128,7 +129,22 @@
      */
     public void setAudioRoute(int route) {
         try {
-            mAdapter.setAudioRoute(route);
+            mAdapter.setAudioRoute(route, null);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified. A list of
+     * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+     */
+    public void requestBluetoothAudio(String bluetoothAddress) {
+        try {
+            mAdapter.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
         } catch (RemoteException e) {
         }
     }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index e384d46..d558bba 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -16,9 +16,11 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.Service;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -377,6 +379,22 @@
     }
 
     /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified if the
+     * bluetooth stack is unable to route audio to the requested device.
+     * A list of available devices can be obtained via
+     * {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     *                         {@link BluetoothDevice#getAddress()}.
+     */
+    public final void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+        if (mPhone != null) {
+            mPhone.requestBluetoothAudio(bluetoothAddress);
+        }
+    }
+
+    /**
      * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
      * to start displaying in-call information to the user. Each instance of {@code InCallService}
      * will have only one {@code Phone}, and this method will be called exactly once in the lifetime
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 066f6c2..421b1a4 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,7 +17,9 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.util.ArrayMap;
 
 import java.util.Collections;
@@ -295,6 +297,18 @@
     }
 
     /**
+     * Request audio routing to a specific bluetooth device. Calling this method may result in
+     * the device routing audio to a different bluetooth device than the one specified. A list of
+     * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+     *
+     * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+     * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+     */
+    public void requestBluetoothAudio(String bluetoothAddress) {
+        mInCallAdapter.requestBluetoothAudio(bluetoothAddress);
+    }
+
+    /**
      * Turns the proximity sensor on. When this request is made, the proximity sensor will
      * become active, and the touch screen and display will be turned off when the user's face
      * is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2cc4314..85906ad 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -398,7 +398,8 @@
         }
 
         @Override
-        public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+        public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+                Session.Info sessionInfo) {
             if (hasConnection(callId)) {
                 // TODO(3pcalls): handle this for remote connections.
                 // Likely we don't want to do anything since it doesn't make sense for self-managed
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index d20da18..da2015f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -103,7 +103,8 @@
 
     void removeExtras(String callId, in List<String> keys, in Session.Info sessionInfo);
 
-    void setAudioRoute(String callId, int audioRoute, in Session.Info sessionInfo);
+    void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+            in Session.Info sessionInfo);
 
     void onConnectionEvent(String callId, String event, in Bundle extras,
     in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 73fa29a..bce3392 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -39,7 +39,7 @@
 
     void mute(boolean shouldMute);
 
-    void setAudioRoute(int route);
+    void setAudioRoute(int route, String bluetoothAddress);
 
     void playDtmfTone(String callId, char digit);
 
diff --git a/tests/FeatureSplit/feature1/Android.mk b/tests/FeatureSplit/feature1/Android.mk
index b3ea97b..e6ba5c2 100644
--- a/tests/FeatureSplit/feature1/Android.mk
+++ b/tests/FeatureSplit/feature1/Android.mk
@@ -26,6 +26,6 @@
 LOCAL_RES_LIBRARIES := FeatureSplitBase
 
 LOCAL_AAPT_FLAGS += --package-id 0x80
-LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.test.split.feature
+LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.one
 
 include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml
index 4e7d151..b87361f 100644
--- a/tests/FeatureSplit/feature1/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature1/AndroidManifest.xml
@@ -15,13 +15,13 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.split.feature.one"
+    package="com.android.test.split.feature"
     featureSplit="feature1">
 
     <uses-sdk android:minSdkVersion="21" />
 
     <application>
-        <activity android:name=".One" android:label="Feature One">
+        <activity android:name=".one.One" android:label="Feature One">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml
index 0e3e73c..7d58865 100644
--- a/tests/FeatureSplit/feature1/res/values/values.xml
+++ b/tests/FeatureSplit/feature1/res/values/values.xml
@@ -20,7 +20,7 @@
     <integer name="test_integer2">200</integer>
     <color name="test_color2">#00ff00</color>
     <string-array name="string_array2">
-        <item>@*com.android.test.split.feature:string/app_title</item>
+        <item>@string/app_title</item>
     </string-array>
 </resources>
 
diff --git a/tests/FeatureSplit/feature2/Android.mk b/tests/FeatureSplit/feature2/Android.mk
index e2fd903..c8e8609 100644
--- a/tests/FeatureSplit/feature2/Android.mk
+++ b/tests/FeatureSplit/feature2/Android.mk
@@ -26,6 +26,6 @@
 LOCAL_RES_LIBRARIES := FeatureSplitBase
 
 LOCAL_AAPT_FLAGS += --package-id 0x81
-LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.test.split.feature
+LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.two
 
 include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature2/AndroidManifest.xml b/tests/FeatureSplit/feature2/AndroidManifest.xml
index bfe6f38..abd0b5e 100644
--- a/tests/FeatureSplit/feature2/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature2/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.split.feature.two"
+    package="com.android.test.split.feature"
     featureSplit="feature2">
 
     <uses-sdk android:minSdkVersion="21" />
diff --git a/tests/FeatureSplit/feature2/res/values/values.xml b/tests/FeatureSplit/feature2/res/values/values.xml
index 2fa6f90..af5ed1b 100644
--- a/tests/FeatureSplit/feature2/res/values/values.xml
+++ b/tests/FeatureSplit/feature2/res/values/values.xml
@@ -18,7 +18,7 @@
     <integer name="test_integer3">300</integer>
     <color name="test_color3">#0000ff</color>
     <string-array name="string_array3">
-        <item>@*com.android.test.split.feature:string/app_title</item>
+        <item>@string/app_title</item>
     </string-array>
 </resources>
 
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index b38d259..a782cd3 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -118,7 +118,7 @@
 bool Reference::Flatten(android::Res_value* out_value) const {
   const ResourceId resid = id.value_or_default(ResourceId(0));
   const bool dynamic = resid.is_valid_dynamic() && resid.package_id() != kFrameworkPackageId &&
-                       resid.package_id() != kAppPackageId;
+                       resid.package_id() < kAppPackageId;
 
   if (reference_type == Reference::Type::kResource) {
     if (dynamic) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 89ae9e8..a34d51b 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -37,35 +37,44 @@
 
 namespace aapt {
 
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
-                       const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
-  BigBuffer buffer(4096);
-  XmlFlattenerOptions options = {};
-  options.use_utf16 = utf16;
-  XmlFlattener flattener(&buffer, options);
-  if (!flattener.Consume(context, &xml)) {
-    return false;
+class ISerializer {
+ public:
+  ISerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {}
+
+  virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
+                            IArchiveWriter* writer) = 0;
+  virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
+  virtual bool SerializeFile(const FileReference* file, IArchiveWriter* writer) = 0;
+
+  bool CopyFileToArchive(const FileReference* file, IArchiveWriter* writer) {
+    uint32_t compression_flags = file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+    if (!io::CopyFileToArchive(context_, file->file, *file->path, compression_flags, writer)) {
+      context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                        << "failed to copy file " << *file->path);
+      return false;
+    }
+
+    return true;
   }
-  io::BigBufferInputStream input_stream(&buffer);
-  return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
-                                      writer);
-}
+
+  virtual ~ISerializer() = default;
+
+ protected:
+  IAaptContext* context_;
+  Source source_;
+};
 
 bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
-                                const TableFlattenerOptions& options, IArchiveWriter* writer) {
-  if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+                                ISerializer* serializer, IArchiveWriter* writer) {
+  if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                     << "failed to serialize AndroidManifest.xml");
     return false;
   }
 
-  BigBuffer buffer(4096);
-  TableFlattener table_flattener(options, &buffer);
-  if (!table_flattener.Consume(context, apk->GetResourceTable())) {
-    return false;
-  }
-
-  io::BigBufferInputStream input_stream(&buffer);
-  if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
-                                    ArchiveEntry::kAlign, writer)) {
+  if (!serializer->SerializeTable(apk->GetResourceTable(), writer)) {
+    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                     << "failed to serialize the resource table");
     return false;
   }
 
@@ -76,51 +85,16 @@
           const FileReference* file = ValueCast<FileReference>(config_value->value.get());
           if (file != nullptr) {
             if (file->file == nullptr) {
-              context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+              context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
                                               << "no file associated with " << *file);
               return false;
             }
 
-            if (file->type == ResourceFile::Type::kProtoXml) {
-              unique_ptr<io::InputStream> in = file->file->OpenInputStream();
-              if (in == nullptr) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to open file " << *file->path);
-                return false;
-              }
-
-              pb::XmlNode pb_node;
-              io::ZeroCopyInputAdaptor adaptor(in.get());
-              if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to parse proto XML " << *file->path);
-                return false;
-              }
-
-              std::string error;
-              unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
-              if (xml == nullptr) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to deserialize proto XML "
-                                                 << *file->path << ": " << error);
-                return false;
-              }
-
-              if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to serialize XML " << *file->path);
-                return false;
-              }
-            } else {
-              if (!io::CopyFileToArchive(context, file->file, *file->path,
-                                         file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
-                                         writer)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to copy file " << *file->path);
-                return false;
-              }
+            if (!serializer->SerializeFile(file, writer)) {
+              context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                               << "failed to serialize file " << *file->path);
+              return false;
             }
-
           } // file
         } // config_value
       } // entry
@@ -129,6 +103,84 @@
   return true;
 }
 
+
+class BinarySerializer : public ISerializer {
+ public:
+  BinarySerializer(IAaptContext* context, const Source& source,
+                   const TableFlattenerOptions& options)
+      : ISerializer(context, source), tableFlattenerOptions_(options) {}
+
+  bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
+                    IArchiveWriter* writer) {
+    BigBuffer buffer(4096);
+    XmlFlattenerOptions options = {};
+    options.use_utf16 = utf16;
+    XmlFlattener flattener(&buffer, options);
+    if (!flattener.Consume(context_, xml)) {
+      return false;
+    }
+
+    io::BigBufferInputStream input_stream(&buffer);
+    return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
+                                        writer);
+  }
+
+  bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) {
+    BigBuffer buffer(4096);
+    TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
+    if (!table_flattener.Consume(context_, table)) {
+      return false;
+    }
+
+    io::BigBufferInputStream input_stream(&buffer);
+    return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
+                                        ArchiveEntry::kAlign, writer);
+  }
+
+  bool SerializeFile(const FileReference* file, IArchiveWriter* writer) {
+    if (file->type == ResourceFile::Type::kProtoXml) {
+      unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+      if (in == nullptr) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to open file " << *file->path);
+        return false;
+      }
+
+      pb::XmlNode pb_node;
+      io::ZeroCopyInputAdaptor adaptor(in.get());
+      if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to parse proto XML " << *file->path);
+        return false;
+      }
+
+      std::string error;
+      unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+      if (xml == nullptr) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to deserialize proto XML "
+                                          << *file->path << ": " << error);
+        return false;
+      }
+
+      if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
+        context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                          << "failed to serialize proto XML " << *file->path);
+        return false;
+      }
+    } else {
+      return CopyFileToArchive(file, writer);
+    }
+
+    return true;
+  }
+
+ private:
+  TableFlattenerOptions tableFlattenerOptions_;
+
+  DISALLOW_COPY_AND_ASSIGN(BinarySerializer);
+};
+
 class Context : public IAaptContext {
  public:
   Context() : mangler_({}), symbols_(&mangler_) {
@@ -222,7 +274,8 @@
   if (writer == nullptr) {
     return 1;
   }
-  return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+  BinarySerializer serializer(&context, apk->GetSource(), options);
+  return ConvertProtoApkToBinaryApk(&context, std::move(apk), &serializer, writer.get()) ? 0 : 1;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index b372923..66b5a7a 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -795,6 +795,9 @@
     for (auto& entry : asset_source->GetAssignedPackageIds()) {
       if (entry.first > kFrameworkPackageId && entry.first < kAppPackageId) {
         final_table_.included_packages_[entry.first] = entry.second;
+      } else if (entry.first == kAppPackageId) {
+        // Capture the included base feature package.
+        included_feature_base_ = entry.second;
       }
     }
 
@@ -1465,11 +1468,48 @@
       return false;
     }
 
-    if (!FlattenTable(table, options_.output_format, writer)) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
-      return false;
+    // Hack to fix b/68820737.
+    // We need to modify the ResourceTable's package name, but that should NOT affect
+    // anything else being generated, which includes the Java classes.
+    // If required, the package name is modifed before flattening, and then modified back
+    // to its original name.
+    ResourceTablePackage* package_to_rewrite = nullptr;
+    if (context_->GetPackageId() > kAppPackageId &&
+        included_feature_base_ == make_value(context_->GetCompilationPackage())) {
+      // The base APK is included, and this is a feature split. If the base package is
+      // the same as this package, then we are building an old style Android Instant Apps feature
+      // split and must apply this workaround to avoid requiring namespaces support.
+      package_to_rewrite = table->FindPackage(context_->GetCompilationPackage());
+      if (package_to_rewrite != nullptr) {
+        CHECK_EQ(1u, table->packages.size()) << "can't change name of package when > 1 package";
+
+        std::string new_package_name =
+            StringPrintf("%s.%s", package_to_rewrite->name.c_str(),
+                         app_info_.split_name.value_or_default("feature").c_str());
+
+        if (context_->IsVerbose()) {
+          context_->GetDiagnostics()->Note(
+              DiagMessage() << "rewriting resource package name for feature split to '"
+                            << new_package_name << "'");
+        }
+        package_to_rewrite->name = new_package_name;
+      }
     }
-    return true;
+
+    bool success = FlattenTable(table, options_.output_format, writer);
+
+    if (package_to_rewrite != nullptr) {
+      // Change the name back.
+      package_to_rewrite->name = context_->GetCompilationPackage();
+      if (package_to_rewrite->id) {
+        table->included_packages_.erase(package_to_rewrite->id.value());
+      }
+    }
+
+    if (!success) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
+    }
+    return success;
   }
 
   int Run(const std::vector<std::string>& input_files) {
@@ -1498,8 +1538,8 @@
       return 1;
     }
 
-    const AppInfo& app_info = maybe_app_info.value();
-    context_->SetMinSdkVersion(app_info.min_sdk_version.value_or_default(0));
+    app_info_ = maybe_app_info.value();
+    context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0));
 
     context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
 
@@ -1699,7 +1739,7 @@
 
         // Generate an AndroidManifest.xml for each split.
         std::unique_ptr<xml::XmlResource> split_manifest =
-            GenerateSplitManifest(app_info, *split_constraints_iter);
+            GenerateSplitManifest(app_info_, *split_constraints_iter);
 
         XmlReferenceLinker linker;
         if (!linker.Consume(context_, split_manifest.get())) {
@@ -1799,6 +1839,8 @@
   LinkContext* context_;
   ResourceTable final_table_;
 
+  AppInfo app_info_;
+
   std::unique_ptr<TableMerger> table_merger_;
 
   // A pointer to the FileCollection representing the filesystem (not archives).
@@ -1816,6 +1858,9 @@
 
   // The set of shared libraries being used, mapping their assigned package ID to package name.
   std::map<size_t, std::string> shared_libs_;
+
+  // The package name of the base application, if it is included.
+  Maybe<std::string> included_feature_base_;
 };
 
 int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index d39f43e..8b3a670 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -140,12 +140,27 @@
   return decl;
 }
 
+// Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by
+// replacing nonconforming characters with underscores.
+//
+// See frameworks/base/core/java/android/content/pm/PackageParser.java which
+// checks this at runtime.
 static std::string MakePackageSafeName(const std::string &name) {
   std::string result(name);
+  bool first = true;
   for (char &c : result) {
-    if (c == '-') {
-      c = '_';
+    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+      first = false;
+      continue;
     }
+    if (!first) {
+      if (c >= '0' && c <= '9') {
+        continue;
+      }
+    }
+
+    c = '_';
+    first = false;
   }
   return result;
 }
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 9c33135..0c527f6 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -24,15 +24,16 @@
 
 TEST(UtilTest, SplitNamesAreSanitized) {
     AppInfo app_info{"com.pkg"};
-    SplitConstraints split_constraints{{test::ParseConfigOrDie("en-rUS-land")}};
+    SplitConstraints split_constraints{
+        {test::ParseConfigOrDie("en-rUS-land"), test::ParseConfigOrDie("b+sr+Latn")}};
 
     const auto doc = GenerateSplitManifest(app_info, split_constraints);
     const auto &root = doc->root;
     EXPECT_EQ(root->name, "manifest");
-    // split names cannot contain hyphens
-    EXPECT_EQ(root->FindAttribute("", "split")->value, "config.en_rUS_land");
+    // split names cannot contain hyphens or plus signs.
+    EXPECT_EQ(root->FindAttribute("", "split")->value, "config.b_sr_Latn_en_rUS_land");
     // but we should use resource qualifiers verbatim in 'targetConfig'.
-    EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "en-rUS-land");
+    EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "b+sr+Latn,en-rUS-land");
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 4a1b46c..2a51df3 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -23,6 +23,7 @@
 
 #include "android-base/logging.h"
 #include "android-base/macros.h"
+#include "android-base/stringprintf.h"
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
@@ -579,7 +580,15 @@
       // Write a self mapping entry for this package if the ID is non-standard (0x7f).
       const uint8_t package_id = package->id.value();
       if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
-        table->included_packages_[package_id] = package->name;
+        auto result = table->included_packages_.insert({package_id, package->name});
+        if (!result.second && result.first->second != package->name) {
+          // A mapping for this package ID already exists, and is a different package. Error!
+          context->GetDiagnostics()->Error(
+              DiagMessage() << android::base::StringPrintf(
+                  "can't map package ID %02x to '%s'. Already mapped to '%s'", package_id,
+                  package->name.c_str(), result.first->second.c_str()));
+          return false;
+        }
       }
     }