Merge "Customizable corner radius for dialogs"
diff --git a/Android.bp b/Android.bp
index c89cc40..5c1ccb7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,6 +47,7 @@
             srcs: [
                 "core/proto/**/*.proto",
                 "libs/incident/**/*.proto",
+                "tools/streaming_proto/stream.proto",
             ],
         },
         android: {
@@ -61,7 +62,7 @@
                 "core/proto/android/os/pagetypeinfo.proto",
                 "core/proto/android/os/procrank.proto",
                 "core/proto/android/service/graphicsstats.proto",
-                "libs/incident/proto/android/privacy.proto",
+                "tools/streaming_proto/stream.proto",
             ],
             shared: {
                 enabled: false,
@@ -70,6 +71,27 @@
     },
 }
 
+gensrcs {
+    name: "gen-platform-proto-constants",
+    depfile: true,
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-cppstream",
+    ],
+
+    srcs: [
+        "core/proto/android/os/kernelwake.proto",
+        "core/proto/android/os/pagetypeinfo.proto",
+        "core/proto/android/os/procrank.proto",
+    ],
+
+    // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
+    cmd: "PATH=$$PATH:$$(dirname $(location protoc-gen-cppstream)) $(location aprotoc) --plugin=protoc-gen-cpp-stream=$(location protoc-gen-cppstream) --dependency_out=$(depfile) --cppstream_out=$(genDir)/ -Iexternal/protobuf/src -I . $(in)",
+
+    output_extension = "proto.h",
+}
+
 subdirs = [
     "cmds/*",
     "core/*",
diff --git a/Android.mk b/Android.mk
index 9843f17..1ed8a25 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1553,6 +1553,7 @@
     -Iexternal/protobuf/src
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 LOCAL_SRC_FILES := \
+    tools/streaming_proto/stream.proto \
     $(call all-proto-files-under, core/proto) \
     $(call all-proto-files-under, libs/incident/proto)
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/api/current.txt b/api/current.txt
index 631c62c..7074de5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12017,6 +12017,7 @@
     method public java.lang.String[] getColumnNames();
     method public int getCount();
     method public android.database.sqlite.SQLiteDatabase getDatabase();
+    method public void setFillWindowForwardOnly(boolean);
     method public void setSelectionArguments(java.lang.String[]);
   }
 
@@ -18271,13 +18272,11 @@
     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,12 +18284,8 @@
     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();
@@ -18301,19 +18296,13 @@
     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);
@@ -18322,15 +18311,10 @@
     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);
@@ -18338,15 +18322,12 @@
     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);
@@ -25835,7 +25816,6 @@
 
   public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
     method public void close();
-    method protected void finalize();
     method public int getSpi();
   }
 
@@ -39945,8 +39925,8 @@
     field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
     field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
-    field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
+    field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
     field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
diff --git a/api/system-current.txt b/api/system-current.txt
index 199e2a7..af15a00 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -88,6 +88,7 @@
     field public static final java.lang.String CAPTURE_SECURE_VIDEO_OUTPUT = "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT";
     field public static final java.lang.String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
     field public static final java.lang.String CAPTURE_VIDEO_OUTPUT = "android.permission.CAPTURE_VIDEO_OUTPUT";
+    field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
     field public static final java.lang.String CHANGE_COMPONENT_ENABLED_STATE = "android.permission.CHANGE_COMPONENT_ENABLED_STATE";
     field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
     field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
@@ -7677,6 +7678,7 @@
     method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
     method public android.app.usage.UsageEvents queryEvents(long, long);
     method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
+    method public void setAppStandbyBucket(java.lang.String, int);
     method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle);
     field public static final int INTERVAL_BEST = 4; // 0x4
     field public static final int INTERVAL_DAILY = 0; // 0x0
@@ -10235,6 +10237,7 @@
     field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
     field public static final java.lang.String EXTRA_QUICK_VIEW_FEATURES = "android.intent.extra.QUICK_VIEW_FEATURES";
     field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
+    field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON";
     field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
     field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
     field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
@@ -12761,6 +12764,7 @@
     method public java.lang.String[] getColumnNames();
     method public int getCount();
     method public android.database.sqlite.SQLiteDatabase getDatabase();
+    method public void setFillWindowForwardOnly(boolean);
     method public void setSelectionArguments(java.lang.String[]);
   }
 
@@ -19827,13 +19831,11 @@
     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();
@@ -19841,12 +19843,8 @@
     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();
@@ -19857,19 +19855,13 @@
     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);
@@ -19878,15 +19870,10 @@
     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);
@@ -19894,15 +19881,12 @@
     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);
@@ -28077,7 +28061,6 @@
 
   public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
     method public void close();
-    method protected void finalize();
     method public int getSpi();
   }
 
@@ -43461,8 +43444,8 @@
     field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
     field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
-    field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
+    field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
     field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
diff --git a/api/test-current.txt b/api/test-current.txt
index 5bae5bd..0bc0cd9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3874,6 +3874,7 @@
     method public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
     method public deprecated void restartPackage(java.lang.String);
     method public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
+    method public void setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect) throws java.lang.SecurityException;
     method public static void setVrThread(int);
     method public void setWatchHeapLimit(long);
     method public static boolean supportsMultiWindow(android.content.Context);
@@ -3887,6 +3888,8 @@
     field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1
     field public static final int RECENT_IGNORE_UNAVAILABLE = 2; // 0x2
     field public static final int RECENT_WITH_EXCLUDED = 1; // 0x1
+    field public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; // 0x1
+    field public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; // 0x0
   }
 
   public static class ActivityManager.AppTask {
@@ -12104,6 +12107,7 @@
     method public java.lang.String[] getColumnNames();
     method public int getCount();
     method public android.database.sqlite.SQLiteDatabase getDatabase();
+    method public void setFillWindowForwardOnly(boolean);
     method public void setSelectionArguments(java.lang.String[]);
   }
 
@@ -18413,13 +18417,11 @@
     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();
@@ -18427,12 +18429,8 @@
     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,19 +18441,13 @@
     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);
@@ -18464,15 +18456,10 @@
     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);
@@ -18480,15 +18467,12 @@
     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);
@@ -26035,7 +26019,6 @@
 
   public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
     method public void close();
-    method protected void finalize();
     method public int getSpi();
   }
 
@@ -40339,8 +40322,8 @@
     field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
     field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
-    field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
+    field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
     field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -41343,6 +41326,7 @@
   }
 
   public final class FileServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    ctor public FileServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>);
     method public int describeContents();
     method public java.util.List<android.telephony.mbms.FileInfo> getFiles();
     method public void writeToParcel(android.os.Parcel, int);
@@ -41451,6 +41435,22 @@
 
 package android.telephony.mbms.vendor {
 
+  public class MbmsDownloadServiceBase extends android.os.Binder {
+    ctor public MbmsDownloadServiceBase();
+    method public int cancelDownload(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
+    method public void dispose(int) throws android.os.RemoteException;
+    method public int download(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
+    method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.os.RemoteException;
+    method public int initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback) throws android.os.RemoteException;
+    method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads(int) throws android.os.RemoteException;
+    method public void onAppCallbackDied(int, int);
+    method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException;
+    method public int requestUpdateFileServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
+    method public int resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
+    method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
+    method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException;
+  }
+
   public class MbmsStreamingServiceBase extends android.os.Binder {
     ctor public MbmsStreamingServiceBase();
     method public void dispose(int) throws android.os.RemoteException;
@@ -44981,25 +44981,25 @@
     field public static final long FIELD_COUNT_UNKNOWN = 0L; // 0x0L
     field public static final int FIELD_ID_MASK = -8; // 0xfffffff8
     field public static final int FIELD_ID_SHIFT = 3; // 0x3
-    field public static final long FIELD_TYPE_BOOL = 55834574848L; // 0xd00000000L
-    field public static final long FIELD_TYPE_BYTES = 64424509440L; // 0xf00000000L
+    field public static final long FIELD_TYPE_BOOL = 34359738368L; // 0x800000000L
+    field public static final long FIELD_TYPE_BYTES = 51539607552L; // 0xc00000000L
     field public static final long FIELD_TYPE_DOUBLE = 4294967296L; // 0x100000000L
-    field public static final long FIELD_TYPE_ENUM = 68719476736L; // 0x1000000000L
-    field public static final long FIELD_TYPE_FIXED32 = 38654705664L; // 0x900000000L
-    field public static final long FIELD_TYPE_FIXED64 = 42949672960L; // 0xa00000000L
+    field public static final long FIELD_TYPE_ENUM = 60129542144L; // 0xe00000000L
+    field public static final long FIELD_TYPE_FIXED32 = 30064771072L; // 0x700000000L
+    field public static final long FIELD_TYPE_FIXED64 = 25769803776L; // 0x600000000L
     field public static final long FIELD_TYPE_FLOAT = 8589934592L; // 0x200000000L
-    field public static final long FIELD_TYPE_INT32 = 12884901888L; // 0x300000000L
-    field public static final long FIELD_TYPE_INT64 = 17179869184L; // 0x400000000L
+    field public static final long FIELD_TYPE_INT32 = 21474836480L; // 0x500000000L
+    field public static final long FIELD_TYPE_INT64 = 12884901888L; // 0x300000000L
     field public static final long FIELD_TYPE_MASK = 1095216660480L; // 0xff00000000L
-    field public static final long FIELD_TYPE_OBJECT = 73014444032L; // 0x1100000000L
-    field public static final long FIELD_TYPE_SFIXED32 = 47244640256L; // 0xb00000000L
-    field public static final long FIELD_TYPE_SFIXED64 = 51539607552L; // 0xc00000000L
+    field public static final long FIELD_TYPE_MESSAGE = 47244640256L; // 0xb00000000L
+    field public static final long FIELD_TYPE_SFIXED32 = 64424509440L; // 0xf00000000L
+    field public static final long FIELD_TYPE_SFIXED64 = 68719476736L; // 0x1000000000L
     field public static final int FIELD_TYPE_SHIFT = 32; // 0x20
-    field public static final long FIELD_TYPE_SINT32 = 30064771072L; // 0x700000000L
-    field public static final long FIELD_TYPE_SINT64 = 34359738368L; // 0x800000000L
-    field public static final long FIELD_TYPE_STRING = 60129542144L; // 0xe00000000L
-    field public static final long FIELD_TYPE_UINT32 = 21474836480L; // 0x500000000L
-    field public static final long FIELD_TYPE_UINT64 = 25769803776L; // 0x600000000L
+    field public static final long FIELD_TYPE_SINT32 = 73014444032L; // 0x1100000000L
+    field public static final long FIELD_TYPE_SINT64 = 77309411328L; // 0x1200000000L
+    field public static final long FIELD_TYPE_STRING = 38654705664L; // 0x900000000L
+    field public static final long FIELD_TYPE_UINT32 = 55834574848L; // 0xd00000000L
+    field public static final long FIELD_TYPE_UINT64 = 17179869184L; // 0x400000000L
     field public static final long FIELD_TYPE_UNKNOWN = 0L; // 0x0L
     field public static final java.lang.String TAG = "ProtoOutputStream";
     field public static final int WIRE_TYPE_END_GROUP = 4; // 0x4
diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto
index 12a18a2..8e29f96 100644
--- a/cmds/am/proto/instrumentation_data.proto
+++ b/cmds/am/proto/instrumentation_data.proto
@@ -28,6 +28,7 @@
     optional double value_double = 5;
     optional sint64 value_long = 6;
     optional ResultsBundle value_bundle = 7;
+    optional bytes value_bytes = 8;
 }
 
 message ResultsBundle {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 79e7fac..813335a 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -160,7 +160,11 @@
             } else if (opt.equals("-r")) {
                 instrument.rawMode = true;
             } else if (opt.equals("-m")) {
-                instrument.proto = true;
+                instrument.protoStd = true;
+            } else if (opt.equals("-f")) {
+                instrument.protoFile = true;
+                if (peekNextArg() != null && !peekNextArg().startsWith("-"))
+                    instrument.logPath = nextArg();
             } else if (opt.equals("-e")) {
                 final String argKey = nextArgRequired();
                 final String argValue = nextArgRequired();
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index b69ef1c..93b9f58 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -25,23 +25,32 @@
 import android.content.pm.InstrumentationInfo;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.AndroidException;
 import android.util.proto.ProtoOutputStream;
 import android.view.IWindowManager;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 
 
 /**
  * Runs the am instrument command
  */
 public class Instrument {
+    public static final String DEFAULT_LOG_DIR = "instrument-logs";
+
     private final IActivityManager mAm;
     private final IPackageManager mPm;
     private final IWindowManager mWm;
@@ -50,7 +59,9 @@
     public String profileFile = null;
     public boolean wait = false;
     public boolean rawMode = false;
-    public boolean proto = false;
+    boolean protoStd = false;  // write proto to stdout
+    boolean protoFile = false;  // write proto to a file
+    String logPath = null;
     public boolean noWindowAnimation = false;
     public String abi = null;
     public int userId = UserHandle.USER_CURRENT;
@@ -178,18 +189,49 @@
      * Printer for the protobuf based status reporting.
      */
     private class ProtoStatusReporter implements StatusReporter {
+
+        private File mLog;
+
+        ProtoStatusReporter() {
+            if (protoFile) {
+                if (logPath == null) {
+                    File logDir = new File(Environment.getLegacyExternalStorageDirectory(),
+                            DEFAULT_LOG_DIR);
+                    if (!logDir.exists() && !logDir.mkdirs()) {
+                        System.err.format("Unable to create log directory: %s\n",
+                                logDir.getAbsolutePath());
+                        protoFile = false;
+                        return;
+                    }
+                    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US);
+                    String fileName = String.format("log-%s.instrumentation_data_proto",
+                            format.format(new Date()));
+                    mLog = new File(logDir, fileName);
+                } else {
+                    mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath);
+                    File logDir = mLog.getParentFile();
+                    if (!logDir.exists() && !logDir.mkdirs()) {
+                        System.err.format("Unable to create log directory: %s\n",
+                                logDir.getAbsolutePath());
+                        protoFile = false;
+                        return;
+                    }
+                }
+                if (mLog.exists()) mLog.delete();
+            }
+        }
+
         @Override
         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
                 Bundle results) {
             final ProtoOutputStream proto = new ProtoOutputStream();
 
-            final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
-
-            proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+            final long token = proto.start(InstrumentationData.Session.TEST_STATUS);
+            proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
             writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+            proto.end(token);
 
-            proto.endRepeatedObject(token);
-            writeProtoToStdout(proto);
+            outputProto(proto);
         }
 
         @Override
@@ -197,80 +239,87 @@
                 Bundle results) {
             final ProtoOutputStream proto = new ProtoOutputStream();
 
-            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
-            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+            final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+            proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
                     InstrumentationData.SESSION_FINISHED);
-            proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+            proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
             writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+            proto.end(token);
 
-            proto.endObject(token);
-            writeProtoToStdout(proto);
+            outputProto(proto);
         }
 
         @Override
         public void onError(String errorText, boolean commandError) {
             final ProtoOutputStream proto = new ProtoOutputStream();
 
-            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
-            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+            final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+            proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
                     InstrumentationData.SESSION_ABORTED);
-            proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+            proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+            proto.end(token);
 
-            proto.endObject(token);
-            writeProtoToStdout(proto);
+            outputProto(proto);
         }
 
         private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
-            final long bundleToken = proto.startObject(fieldId);
+            final long bundleToken = proto.start(fieldId);
 
             for (final String key: sorted(bundle.keySet())) {
                 final long entryToken = proto.startRepeatedObject(
                         InstrumentationData.ResultsBundle.ENTRIES);
 
-                proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+                proto.write(InstrumentationData.ResultsBundleEntry.KEY, key);
 
                 final Object val = bundle.get(key);
                 if (val instanceof String) {
-                    proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
                             (String)val);
                 } else if (val instanceof Byte) {
-                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT,
                             ((Byte)val).intValue());
                 } else if (val instanceof Double) {
-                    proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
-                            ((Double)val).doubleValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val);
                 } else if (val instanceof Float) {
-                    proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
-                            ((Float)val).floatValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val);
                 } else if (val instanceof Integer) {
-                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
-                            ((Integer)val).intValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val);
                 } else if (val instanceof Long) {
-                    proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
-                            ((Long)val).longValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val);
                 } else if (val instanceof Short) {
-                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
-                            ((Short)val).intValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val);
                 } else if (val instanceof Bundle) {
                     writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
                             (Bundle)val);
+                } else if (val instanceof byte[]) {
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val);
                 }
 
-                proto.endRepeatedObject(entryToken);
+                proto.end(entryToken);
             }
 
-            proto.endObject(bundleToken);
+            proto.end(bundleToken);
         }
 
-        private void writeProtoToStdout(ProtoOutputStream proto) {
-            try {
-                System.out.write(proto.getBytes());
-                System.out.flush();
-            } catch (IOException ex) {
-                System.err.println("Error writing finished response: ");
-                ex.printStackTrace(System.err);
+        private void outputProto(ProtoOutputStream proto) {
+            byte[] out = proto.getBytes();
+            if (protoStd) {
+                try {
+                    System.out.write(out);
+                    System.out.flush();
+                } catch (IOException ex) {
+                    System.err.println("Error writing finished response: ");
+                    ex.printStackTrace(System.err);
+                }
+            }
+            if (protoFile) {
+                try (OutputStream os = new FileOutputStream(mLog, true)) {
+                    os.write(proto.getBytes());
+                    os.flush();
+                } catch (IOException ex) {
+                    System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath());
+                    ex.printStackTrace();
+                }
             }
         }
     }
@@ -374,7 +423,7 @@
 
         try {
             // Choose which output we will do.
-            if (proto) {
+            if (protoFile || protoStd) {
                 reporter = new ProtoStatusReporter();
             } else if (wait) {
                 reporter = new TextStatusReporter(rawMode);
@@ -396,7 +445,7 @@
                 mWm.setAnimationScale(2, 0.0f);
             }
 
-            // Figure out which component we are tring to do.
+            // Figure out which component we are trying to do.
             final ComponentName cn = parseComponentName(componentNameArg);
 
             // Choose an ABI if necessary
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 0532083..2ef0371 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -8,44 +8,53 @@
         "-O0"
     ],
 
-    srcs: [
-        "IncidentHelper.cpp",
-        "ih_util.cpp",
+    local_include_dirs: [
+        "src/",
+        "src/parsers/",
     ],
 
+    srcs: [
+        "src/parsers/*.cpp",
+        "src/TextParserBase.cpp",
+        "src/ih_util.cpp",
+    ],
+
+    generated_headers: ["gen-platform-proto-constants"],
+
     shared_libs: [
         "libbase",
         "liblog",
-        "libprotobuf-cpp-full",
+        "libprotoutil",
         "libutils",
     ],
-
-    static_libs: [
-        "libplatformprotos",
-    ],
 }
 
 cc_binary {
     name: "incident_helper",
     defaults: ["incident_helper_defaults"],
-    srcs: ["main.cpp"],
+    srcs: ["src/main.cpp"],
 }
 
 
 cc_test {
     name: "incident_helper_test",
     defaults: ["incident_helper_defaults"],
+    local_include_dirs: ["src/"],
 
     srcs: [
-        "tests/IncidentHelper_test.cpp",
-        "tests/ih_util_test.cpp",
+        "tests/*.cpp",
     ],
 
     data: [
         "testdata/*",
     ],
 
+    shared_libs: [
+        "libprotobuf-cpp-full",
+    ],
+
     static_libs: [
         "libgmock",
+        "libplatformprotos"
     ],
-}
\ No newline at end of file
+}
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
deleted file mode 100644
index 7b06d42..0000000
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ /dev/null
@@ -1,327 +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 LOG_TAG "incident_helper"
-
-#include "IncidentHelper.h"
-#include "ih_util.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <unistd.h>
-#include <string>
-#include <vector>
-
-using namespace android::base;
-using namespace android::os;
-using namespace google::protobuf;
-using namespace std;
-
-
-static const string TAB_DELIMITER = "\t";
-static const string COMMA_DELIMITER = ",";
-
-static inline int toInt(const string& s) {
-    return atoi(s.c_str());
-}
-
-static inline long toLong(const string& s) {
-    return atol(s.c_str());
-}
-
-/**
- * Sets the given protobuf message when the field name matches one of the
- * fields. It is useful to set values to proto from table-like plain texts.
- */
-static bool
-SetTableField(::google::protobuf::Message* message, string field_name, string field_value) {
-    const Descriptor* descriptor = message->GetDescriptor();
-    const Reflection* reflection = message->GetReflection();
-
-    const FieldDescriptor* field = descriptor->FindFieldByName(field_name);
-    switch (field->type()) {
-        case FieldDescriptor::TYPE_STRING:
-            reflection->SetString(message, field, field_value);
-            return true;
-        case FieldDescriptor::TYPE_INT64:
-            reflection->SetInt64(message, field, toLong(field_value));
-            return true;
-        case FieldDescriptor::TYPE_UINT64:
-            reflection->SetUInt64(message, field, toLong(field_value));
-            return true;
-        case FieldDescriptor::TYPE_INT32:
-            reflection->SetInt32(message, field, toInt(field_value));
-            return true;
-        case FieldDescriptor::TYPE_UINT32:
-            reflection->SetUInt32(message, field, toInt(field_value));
-            return true;
-        default:
-            // Add new scalar types
-            return false;
-    }
-}
-
-// ================================================================================
-status_t NoopParser::Parse(const int in, const int out) const
-{
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-    if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
-        return -1;
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t ReverseParser::Parse(const int in, const int out) const
-{
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-    // reverse the content
-    reverse(content.begin(), content.end());
-    if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
-        return -1;
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t KernelWakesParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    header_t header;  // the header of /d/wakeup_sources
-    record_t record;  // retain each record
-    int nline = 0;
-
-    KernelWakeSources proto;
-
-    // parse line by line
-    while (reader.readLine(&line)) {
-        if (line.empty()) continue;
-        // parse head line
-        if (nline++ == 0) {
-            header = parseHeader(line, TAB_DELIMITER);
-            continue;
-        }
-
-        // parse for each record, the line delimiter is \t only!
-        record = parseRecord(line, TAB_DELIMITER);
-
-        if (record.size() != header.size()) {
-            // TODO: log this to incident report!
-            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
-            continue;
-        }
-
-        WakeupSourceProto* source = proto.add_wakeup_sources();
-        for (int i=0; i<(int)record.size(); i++) {
-            if (!SetTableField(source, header[i], record[i])) {
-                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
-            }
-        }
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!proto.SerializeToFileDescriptor(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
-        return -1;
-    }
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t ProcrankParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    header_t header;  // the header of /d/wakeup_sources
-    record_t record;  // retain each record
-    int nline = 0;
-
-    Procrank proto;
-
-    // parse line by line
-    while (reader.readLine(&line)) {
-        if (line.empty()) continue;
-
-        // parse head line
-        if (nline++ == 0) {
-            header = parseHeader(line);
-            continue;
-        }
-
-        if (hasPrefix(&line, "ZRAM:")) {
-            proto.mutable_summary()->mutable_zram()->set_raw_text(line);
-            continue;
-        }
-        if (hasPrefix(&line, "RAM:")) {
-            proto.mutable_summary()->mutable_ram()->set_raw_text(line);
-            continue;
-        }
-
-        record = parseRecord(line);
-        if (record.size() != header.size()) {
-            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
-                ProcessProto* total = proto.mutable_summary()->mutable_total();
-                for (int i=1; i<=(int)record.size(); i++) {
-                    SetTableField(total, header[header.size() - i], record[record.size() - i]);
-                }
-            } else {
-                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
-                    line.c_str());
-            }
-            continue;
-        }
-
-        ProcessProto* process = proto.add_processes();
-        for (int i=0; i<(int)record.size(); i++) {
-            if (!SetTableField(process, header[i], record[i])) {
-                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
-            }
-        }
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!proto.SerializeToFileDescriptor(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
-        return -1;
-    }
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
-    return NO_ERROR;
-}
-
-// ================================================================================
-status_t PageTypeInfoParser::Parse(const int in, const int out) const {
-    Reader reader(in);
-    string line;
-    bool migrateTypeSession = false;
-    int pageBlockOrder;
-    header_t blockHeader;
-
-    PageTypeInfo pageTypeInfo;
-
-    while (reader.readLine(&line)) {
-        if (line.empty()) {
-            migrateTypeSession = false;
-            blockHeader.clear();
-            continue;
-        }
-
-        if (hasPrefix(&line, "Page block order:")) {
-            pageBlockOrder = toInt(line);
-            pageTypeInfo.set_page_block_order(pageBlockOrder);
-            continue;
-        }
-        if (hasPrefix(&line, "Pages per block:")) {
-            pageTypeInfo.set_pages_per_block(toInt(line));
-            continue;
-        }
-        if (hasPrefix(&line, "Free pages count per migrate type at order")) {
-            migrateTypeSession = true;
-            continue;
-        }
-        if (hasPrefix(&line, "Number of blocks type")) {
-            blockHeader = parseHeader(line);
-            continue;
-        }
-
-        record_t record = parseRecord(line, COMMA_DELIMITER);
-        if (migrateTypeSession && record.size() == 3) {
-            MigrateTypeProto* migrateType = pageTypeInfo.add_migrate_types();
-            // expect part 0 starts with "Node"
-            if (hasPrefix(&record[0], "Node")) {
-                migrateType->set_node(toInt(record[0]));
-            } else goto ERROR;
-            // expect part 1 starts with "zone"
-            if (hasPrefix(&record[1], "zone")) {
-                migrateType->set_zone(record[1]);
-            } else goto ERROR;
-            // expect part 2 starts with "type"
-            if (hasPrefix(&record[2], "type")) {
-                // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
-                // An example looks like:
-                // header line:      type    0   1   2 3 4 5 6 7 8 9 10
-                // record line: Unmovable  426 279 226 1 1 1 0 0 2 2  0
-                // The pageBlockOrder = 10 and it's zero-indexed. so total parts
-                // are 10 + 1(zero-indexed) + 1(the type part) = 12.
-                record_t pageCounts = parseRecord(record[2]);
-                int pageCountsSize = pageBlockOrder + 2;
-                if ((int)pageCounts.size() != pageCountsSize) goto ERROR;
-
-                migrateType->set_type(pageCounts[0]);
-                for (auto i=1; i<pageCountsSize; i++) {
-                    migrateType->add_free_pages_count(toInt(pageCounts[i]));
-                }
-            } else goto ERROR;
-            continue;
-        }
-
-        if (!blockHeader.empty() && record.size() == 2) {
-            BlockProto* block = pageTypeInfo.add_blocks();
-
-            if (hasPrefix(&record[0], "Node")) {
-                block->set_node(toInt(record[0]));
-            } else goto ERROR;
-
-            if (hasPrefix(&record[1], "zone")) {
-                record_t blockCounts = parseRecord(record[1]);
-                block->set_zone(blockCounts[0]);
-                for (size_t i=0; i<blockHeader.size(); i++) {
-                    if (!SetTableField(block, blockHeader[i], blockCounts[i+1])) goto ERROR;
-                }
-            } else goto ERROR;
-
-            continue;
-        }
-
-ERROR:  // print out error for this single line and continue parsing
-        fprintf(stderr, "[%s]Bad line: %s\n", this->name.string(), line.c_str());
-    }
-
-    if (!reader.ok(&line)) {
-        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
-        return -1;
-    }
-
-    if (!pageTypeInfo.SerializeToFileDescriptor(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
-        return -1;
-    }
-
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), pageTypeInfo.ByteSize());
-    return NO_ERROR;
-}
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp
new file mode 100644
index 0000000..a8f9968
--- /dev/null
+++ b/cmds/incident_helper/src/TextParserBase.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 LOG_TAG "incident_helper"
+
+#include "TextParserBase.h"
+
+#include <android-base/file.h>
+
+using namespace android::base;
+using namespace std;
+
+// ================================================================================
+status_t NoopParser::Parse(const int in, const int out) const
+{
+    string content;
+    if (!ReadFdToString(in, &content)) {
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        return -1;
+    }
+    if (!WriteStringToFd(content, out)) {
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        return -1;
+    }
+    return NO_ERROR;
+}
+
+// ================================================================================
+status_t ReverseParser::Parse(const int in, const int out) const
+{
+    string content;
+    if (!ReadFdToString(in, &content)) {
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        return -1;
+    }
+    // reverse the content
+    reverse(content.begin(), content.end());
+    if (!WriteStringToFd(content, out)) {
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        return -1;
+    }
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/src/TextParserBase.h
similarity index 64%
rename from cmds/incident_helper/IncidentHelper.h
rename to cmds/incident_helper/src/TextParserBase.h
index d24d717..c41612d 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/src/TextParserBase.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef INCIDENT_HELPER_H
-#define INCIDENT_HELPER_H
+#ifndef TEXT_PARSER_BASE_H
+#define TEXT_PARSER_BASE_H
 
 #include <utils/Errors.h>
 #include <utils/String8.h>
@@ -68,37 +68,4 @@
     virtual status_t Parse(const int in, const int out) const;
 };
 
-/**
- * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
- */
-class KernelWakesParser : public TextParserBase {
-public:
-    KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
-    ~KernelWakesParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
- */
-class PageTypeInfoParser : public TextParserBase {
-public:
-    PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
-    ~PageTypeInfoParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * Procrank parser, parses text produced by command procrank
- */
-class ProcrankParser : public TextParserBase {
-public:
-    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
-    ~ProcrankParser() {};
-
-    virtual status_t Parse(const int in, const int out) const;
-};
-
-#endif  // INCIDENT_HELPER_H
+#endif // TEXT_PARSER_BASE_H
\ No newline at end of file
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
similarity index 73%
rename from cmds/incident_helper/ih_util.cpp
rename to cmds/incident_helper/src/ih_util.cpp
index 2ab4b54..c7d1ca2 100644
--- a/cmds/incident_helper/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -87,6 +87,15 @@
     return true;
 }
 
+int toInt(const std::string& s) {
+    return atoi(s.c_str());
+}
+
+long long toLongLong(const std::string& s) {
+    return atoll(s.c_str());
+}
+
+// ==============================================================================
 Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
 
 Reader::Reader(const int fd, const size_t capacity)
@@ -151,3 +160,57 @@
     error->assign(mStatus);
     return mStatus.empty();
 }
+
+// ==============================================================================
+Table::Table(const char* names[], const uint64_t ids[], const int count)
+        :mFieldNames(names),
+         mFieldIds(ids),
+         mFieldCount(count)
+{
+}
+
+Table::~Table()
+{
+}
+
+bool
+Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
+{
+    uint64_t found = 0;
+    for (int i=0; i<mFieldCount; i++) {
+        if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
+            found = mFieldIds[i];
+            break;
+        }
+    }
+
+    switch (found & FIELD_TYPE_MASK) {
+        case FIELD_TYPE_DOUBLE:
+        case FIELD_TYPE_FLOAT:
+            // TODO: support parse string to float/double
+            return false;
+        case FIELD_TYPE_STRING:
+        case FIELD_TYPE_BYTES:
+            proto.write(found, value);
+            break;
+        case FIELD_TYPE_INT64:
+        case FIELD_TYPE_SINT64:
+        case FIELD_TYPE_UINT64:
+        case FIELD_TYPE_FIXED64:
+        case FIELD_TYPE_SFIXED64:
+            proto.write(found, toLongLong(value));
+            break;
+        case FIELD_TYPE_BOOL:
+        case FIELD_TYPE_ENUM:
+        case FIELD_TYPE_INT32:
+        case FIELD_TYPE_SINT32:
+        case FIELD_TYPE_UINT32:
+        case FIELD_TYPE_FIXED32:
+        case FIELD_TYPE_SFIXED32:
+            proto.write(found, toInt(value));
+            break;
+        default:
+            return false;
+    }
+    return true;
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/src/ih_util.h
similarity index 79%
rename from cmds/incident_helper/ih_util.h
rename to cmds/incident_helper/src/ih_util.h
index ce5baee..86761e9 100644
--- a/cmds/incident_helper/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -21,6 +21,10 @@
 #include <vector>
 #include <sstream>
 
+#include <android/util/ProtoOutputStream.h>
+
+using namespace android::util;
+
 typedef std::vector<std::string> header_t;
 typedef std::vector<std::string> record_t;
 typedef std::string (*trans_func) (const std::string&);
@@ -52,6 +56,12 @@
 bool hasPrefix(std::string* line, const char* key);
 
 /**
+ * Converts string to the desired type
+ */
+int toInt(const std::string& s);
+long long toLongLong(const std::string& s);
+
+/**
  * Reader class reads data from given fd in streaming fashion.
  * The buffer size is controlled by capacity parameter.
  */
@@ -78,4 +88,22 @@
     inline bool EOR() { return mFd == -1 && mBufSize == 0; };
 };
 
+/**
+ * The class contains a mapping between table headers to its field ids.
+ * And allow users to insert the field values to proto based on its header name.
+ */
+class Table
+{
+public:
+    Table(const char* names[], const uint64_t ids[], const int count);
+    ~Table();
+
+    bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
+
+private:
+    const char** mFieldNames;
+    const uint64_t* mFieldIds;
+    const int mFieldCount;
+};
+
 #endif  // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/src/main.cpp
similarity index 96%
rename from cmds/incident_helper/main.cpp
rename to cmds/incident_helper/src/main.cpp
index 52ff777..3da87b9c 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,7 +16,9 @@
 
 #define LOG_TAG "incident_helper"
 
-#include "IncidentHelper.h"
+#include "parsers/KernelWakesParser.h"
+#include "parsers/PageTypeInfoParser.h"
+#include "parsers/ProcrankParser.h"
 
 #include <android-base/file.h>
 #include <getopt.h>
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
new file mode 100644
index 0000000..cc4a1e1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -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.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/kernelwake.proto.h"
+#include "ih_util.h"
+#include "KernelWakesParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = "\t";
+
+status_t
+KernelWakesParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    ProtoOutputStream proto;
+    Table table(WakeupSourceProto::_FIELD_NAMES, WakeupSourceProto::_FIELD_IDS, WakeupSourceProto::_FIELD_COUNT);
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+        // parse head line
+        if (nline++ == 0) {
+            header = parseHeader(line, LINE_DELIMITER);
+            continue;
+        }
+
+        // parse for each record, the line delimiter is \t only!
+        record = parseRecord(line, LINE_DELIMITER);
+
+        if (record.size() != header.size()) {
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
+            continue;
+        }
+
+        long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
+        for (int i=0; i<(int)record.size(); i++) {
+            if (!table.insertField(proto, header[i], record[i])) {
+                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+            }
+        }
+        proto.end(token);
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.h b/cmds/incident_helper/src/parsers/KernelWakesParser.h
new file mode 100644
index 0000000..aabab7c
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.h
@@ -0,0 +1,33 @@
+/*
+ * 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 KERNEL_WAKES_PARSER_H
+#define KERNEL_WAKES_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
+ */
+class KernelWakesParser : public TextParserBase {
+public:
+    KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
+    ~KernelWakesParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // KERNEL_WAKES_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
new file mode 100644
index 0000000..6047bd1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.proto.h"
+#include "ih_util.h"
+#include "PageTypeInfoParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = ",";
+
+status_t
+PageTypeInfoParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    bool migrateTypeSession = false;
+    int pageBlockOrder;
+    header_t blockHeader;
+
+    ProtoOutputStream proto;
+    Table table(BlockProto::_FIELD_NAMES, BlockProto::_FIELD_IDS, BlockProto::_FIELD_COUNT);
+
+    while (reader.readLine(&line)) {
+        if (line.empty()) {
+            migrateTypeSession = false;
+            blockHeader.clear();
+            continue;
+        }
+
+        if (hasPrefix(&line, "Page block order:")) {
+            pageBlockOrder = toInt(line);
+            proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder);
+            continue;
+        }
+        if (hasPrefix(&line, "Pages per block:")) {
+            proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line));
+            continue;
+        }
+        if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+            migrateTypeSession = true;
+            continue;
+        }
+        if (hasPrefix(&line, "Number of blocks type")) {
+            blockHeader = parseHeader(line);
+            continue;
+        }
+
+        record_t record = parseRecord(line, LINE_DELIMITER);
+        if (migrateTypeSession && record.size() == 3) {
+            long long token = proto.start(PageTypeInfo::MIGRATE_TYPES);
+            // expect part 0 starts with "Node"
+            if (hasPrefix(&record[0], "Node")) {
+                proto.write(MigrateTypeProto::NODE, toInt(record[0]));
+            } else return BAD_VALUE;
+            // expect part 1 starts with "zone"
+            if (hasPrefix(&record[1], "zone")) {
+                proto.write(MigrateTypeProto::ZONE, record[1]);
+            } else return BAD_VALUE;
+            // expect part 2 starts with "type"
+            if (hasPrefix(&record[2], "type")) {
+                // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
+                // An example looks like:
+                // header line:      type    0   1   2 3 4 5 6 7 8 9 10
+                // record line: Unmovable  426 279 226 1 1 1 0 0 2 2  0
+                // The pageBlockOrder = 10 and it's zero-indexed. so total parts
+                // are 10 + 1(zero-indexed) + 1(the type part) = 12.
+                record_t pageCounts = parseRecord(record[2]);
+                int pageCountsSize = pageBlockOrder + 2;
+                if ((int)pageCounts.size() != pageCountsSize) return BAD_VALUE;
+
+                proto.write(MigrateTypeProto::TYPE, pageCounts[0]);
+                for (auto i=1; i<pageCountsSize; i++) {
+                    proto.write(MigrateTypeProto::FREE_PAGES_COUNT, toInt(pageCounts[i]));
+                }
+            } else return BAD_VALUE;
+
+            proto.end(token);
+        } else if (!blockHeader.empty() && record.size() == 2) {
+            long long token = proto.start(PageTypeInfo::BLOCKS);
+            if (hasPrefix(&record[0], "Node")) {
+                proto.write(BlockProto::NODE, toInt(record[0]));
+            } else return BAD_VALUE;
+
+            if (hasPrefix(&record[1], "zone")) {
+                record_t blockCounts = parseRecord(record[1]);
+                proto.write(BlockProto::ZONE, blockCounts[0]);
+
+                for (size_t i=0; i<blockHeader.size(); i++) {
+                    if (!table.insertField(proto, blockHeader[i], blockCounts[i+1])) {
+                        return BAD_VALUE;
+                    }
+                }
+            } else return BAD_VALUE;
+            proto.end(token);
+        }
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.h b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
new file mode 100644
index 0000000..fb84d91
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
@@ -0,0 +1,35 @@
+/*
+ * 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 PAGE_TYPE_INFO_PARSER_H
+#define PAGE_TYPE_INFO_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
+ */
+class PageTypeInfoParser : public TextParserBase {
+public:
+    PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
+    ~PageTypeInfoParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PAGE_TYPE_INFO_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
new file mode 100644
index 0000000..93f970f
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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 LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/procrank.proto.h"
+#include "ih_util.h"
+#include "ProcrankParser.h"
+
+using namespace android::os;
+
+status_t
+ProcrankParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    ProtoOutputStream proto;
+    Table table(ProcessProto::_FIELD_NAMES, ProcessProto::_FIELD_IDS, ProcessProto::_FIELD_COUNT);
+    string zram, ram, total;
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        // parse head line
+        if (nline++ == 0) {
+            header = parseHeader(line);
+            continue;
+        }
+
+        if (hasPrefix(&line, "ZRAM:")) {
+            zram = line;
+            continue;
+        }
+        if (hasPrefix(&line, "RAM:")) {
+            ram = line;
+            continue;
+        }
+
+        record = parseRecord(line);
+        if (record.size() != header.size()) {
+            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+                total = line;
+            } else {
+                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+                    line.c_str());
+            }
+            continue;
+        }
+
+        long long token = proto.start(Procrank::PROCESSES);
+        for (int i=0; i<(int)record.size(); i++) {
+            if (!table.insertField(proto, header[i], record[i])) {
+                fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+            }
+        }
+        proto.end(token);
+    }
+
+    // add summary
+    long long token = proto.start(Procrank::SUMMARY);
+    if (!total.empty()) {
+        record = parseRecord(total);
+        long long token = proto.start(SummaryProto::TOTAL);
+        for (int i=(int)record.size(); i>0; i--) {
+            table.insertField(proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
+        }
+        proto.end(token);
+    }
+    if (!zram.empty()) {
+        long long token = proto.start(SummaryProto::ZRAM);
+        proto.write(ZramProto::RAW_TEXT, zram);
+        proto.end(token);
+    }
+    if (!ram.empty()) {
+        long long token = proto.start(SummaryProto::RAM);
+        proto.write(RamProto::RAW_TEXT, ram);
+        proto.end(token);
+    }
+    proto.end(token);
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.h b/cmds/incident_helper/src/parsers/ProcrankParser.h
new file mode 100644
index 0000000..5d0ee48
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.h
@@ -0,0 +1,35 @@
+/*
+ * 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 PROCRANK_PARSER_H
+#define PROCRANK_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+class ProcrankParser : public TextParserBase {
+public:
+    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+    ~ProcrankParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // PROCRANK_PARSER_H
diff --git a/cmds/incident_helper/testdata/kernel_wakeups_short.txt b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
new file mode 100644
index 0000000..a51926e
--- /dev/null
+++ b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
@@ -0,0 +1,3 @@
+name	active_count	last_change
+ab	8	123456123456
+df	143	0
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
deleted file mode 100644
index c44a163..0000000
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ /dev/null
@@ -1,255 +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.
- */
-
-#include "IncidentHelper.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-#include <gmock/gmock.h>
-#include <google/protobuf/message.h>
-#include <gtest/gtest.h>
-#include <string.h>
-#include <fcntl.h>
-
-using namespace android::base;
-using namespace android::os;
-using namespace std;
-using ::testing::StrEq;
-using ::testing::Test;
-using ::testing::internal::CaptureStderr;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStderr;
-using ::testing::internal::GetCapturedStdout;
-
-class IncidentHelperTest : public Test {
-public:
-    virtual void SetUp() override {
-        ASSERT_TRUE(tf.fd != -1);
-    }
-
-    string getSerializedString(::google::protobuf::Message& message) {
-        string expectedStr;
-        message.SerializeToFileDescriptor(tf.fd);
-        ReadFileToString(tf.path, &expectedStr);
-        return expectedStr;
-    }
-
-protected:
-    TemporaryFile tf;
-
-    const string kTestPath = GetExecutableDirectory();
-    const string kTestDataPath = kTestPath + "/testdata/";
-};
-
-TEST_F(IncidentHelperTest, ReverseParser) {
-    ReverseParser parser;
-    TemporaryFile tf;
-
-    ASSERT_TRUE(tf.fd != -1);
-    ASSERT_TRUE(WriteStringToFile("TestData", tf.path, false));
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(tf.fd, STDOUT_FILENO));
-    EXPECT_THAT(GetCapturedStdout(), StrEq("ataDtseT"));
-}
-
-TEST_F(IncidentHelperTest, KernelWakesParser) {
-    const string testFile = kTestDataPath + "kernel_wakeups.txt";
-    KernelWakesParser parser;
-    KernelWakeSources expected;
-
-    WakeupSourceProto* record1 = expected.add_wakeup_sources();
-    record1->set_name("ipc000000ab_ATFWD-daemon");
-    record1->set_active_count(8);
-    record1->set_event_count(8);
-    record1->set_wakeup_count(0);
-    record1->set_expire_count(0);
-    record1->set_active_since(0l);
-    record1->set_total_time(0l);
-    record1->set_max_time(0l);
-    record1->set_last_change(131348l);
-    record1->set_prevent_suspend_time(0l);
-
-    WakeupSourceProto* record2 = expected.add_wakeup_sources();
-    record2->set_name("ipc000000aa_ATFWD-daemon");
-    record2->set_active_count(143);
-    record2->set_event_count(143);
-    record2->set_wakeup_count(0);
-    record2->set_expire_count(0);
-    record2->set_active_since(0l);
-    record2->set_total_time(123l);
-    record2->set_max_time(3l);
-    record2->set_last_change(2067286206l);
-    record2->set_prevent_suspend_time(0l);
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParser) {
-    const string testFile = kTestDataPath + "procrank.txt";
-    ProcrankParser parser;
-    Procrank expected;
-
-    ProcessProto* process1 = expected.add_processes();
-    process1->set_pid(1119);
-    process1->set_vss(2607640);
-    process1->set_rss(339564);
-    process1->set_pss(180278);
-    process1->set_uss(114216);
-    process1->set_swap(1584);
-    process1->set_pswap(46);
-    process1->set_uswap(0);
-    process1->set_zswap(10);
-    process1->set_cmdline("system_server");
-
-    ProcessProto* process2 = expected.add_processes();
-    process2->set_pid(649);
-    process2->set_vss(11016);
-    process2->set_rss(1448);
-    process2->set_pss(98);
-    process2->set_uss(48);
-    process2->set_swap(472);
-    process2->set_pswap(342);
-    process2->set_uswap(212);
-    process2->set_zswap(75);
-    process2->set_cmdline("/vendor/bin/qseecomd");
-
-    ProcessProto* total = expected.mutable_summary()->mutable_total();
-    total->set_pss(1201993);
-    total->set_uss(935300);
-    total->set_swap(88164);
-    total->set_pswap(31069);
-    total->set_uswap(27612);
-    total->set_zswap(6826);
-    total->set_cmdline("TOTAL");
-
-    expected.mutable_summary()->mutable_zram()
-        ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
-    expected.mutable_summary()->mutable_ram()
-        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParserShortHeader) {
-    const string testFile = kTestDataPath + "procrank_short.txt";
-    ProcrankParser parser;
-    Procrank expected;
-
-    ProcessProto* process1 = expected.add_processes();
-    process1->set_pid(1119);
-    process1->set_vss(2607640);
-    process1->set_rss(339564);
-    process1->set_pss(180278);
-    process1->set_uss(114216);
-    process1->set_cmdline("system_server");
-
-    ProcessProto* process2 = expected.add_processes();
-    process2->set_pid(649);
-    process2->set_vss(11016);
-    process2->set_rss(1448);
-    process2->set_pss(98);
-    process2->set_uss(48);
-    process2->set_cmdline("/vendor/bin/qseecomd");
-
-    ProcessProto* total = expected.mutable_summary()->mutable_total();
-    total->set_pss(1201993);
-    total->set_uss(935300);
-    total->set_cmdline("TOTAL");
-
-    expected.mutable_summary()->mutable_ram()
-        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
-
-TEST_F(IncidentHelperTest, PageTypeInfoParser) {
-    const string testFile = kTestDataPath + "pagetypeinfo.txt";
-    PageTypeInfoParser parser;
-    PageTypeInfo expected;
-
-    expected.set_page_block_order(10);
-    expected.set_pages_per_block(1024);
-
-    MigrateTypeProto* mt1 = expected.add_migrate_types();
-    mt1->set_node(0);
-    mt1->set_zone("DMA");
-    mt1->set_type("Unmovable");
-    int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
-    for (auto i=0; i<11; i++) {
-        mt1->add_free_pages_count(arr1[i]);
-    }
-
-    MigrateTypeProto* mt2 = expected.add_migrate_types();
-    mt2->set_node(0);
-    mt2->set_zone("Normal");
-    mt2->set_type("Reclaimable");
-    int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
-    for (auto i=0; i<11; i++) {
-        mt2->add_free_pages_count(arr2[i]);
-    }
-
-    BlockProto* block1 = expected.add_blocks();
-    block1->set_node(0);
-    block1->set_zone("DMA");
-    block1->set_unmovable(74);
-    block1->set_reclaimable(9);
-    block1->set_movable(337);
-    block1->set_cma(41);
-    block1->set_reserve(1);
-    block1->set_isolate(0);
-
-
-    BlockProto* block2 = expected.add_blocks();
-    block2->set_node(0);
-    block2->set_zone("Normal");
-    block2->set_unmovable(70);
-    block2->set_reclaimable(12);
-    block2->set_movable(423);
-    block2->set_cma(0);
-    block2->set_reserve(1);
-    block2->set_isolate(0);
-
-    int fd = open(testFile.c_str(), O_RDONLY);
-    ASSERT_TRUE(fd != -1);
-
-    CaptureStdout();
-    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
-    close(fd);
-}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/KernelWakesParser_test.cpp b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
new file mode 100644
index 0000000..a8fa6208
--- /dev/null
+++ b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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 "KernelWakesParser.h"
+
+#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class KernelWakesParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(KernelWakesParserTest, Short) {
+    const string testFile = kTestDataPath + "kernel_wakeups_short.txt";
+    KernelWakesParser parser;
+    KernelWakeSources expected;
+
+    WakeupSourceProto* record1 = expected.add_wakeup_sources();
+    record1->set_name("ab");
+    record1->set_active_count(8);
+    record1->set_last_change(123456123456LL);
+
+    WakeupSourceProto* record2 = expected.add_wakeup_sources();
+    record2->set_name("df");
+    record2->set_active_count(143);
+    record2->set_last_change(0LL);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
+
+TEST_F(KernelWakesParserTest, Normal) {
+    const string testFile = kTestDataPath + "kernel_wakeups.txt";
+    KernelWakesParser parser;
+    KernelWakeSources expected;
+
+    WakeupSourceProto* record1 = expected.add_wakeup_sources();
+    record1->set_name("ipc000000ab_ATFWD-daemon");
+    record1->set_active_count(8);
+    record1->set_event_count(8);
+    record1->set_wakeup_count(0);
+    record1->set_expire_count(0);
+    record1->set_active_since(0l);
+    record1->set_total_time(0l);
+    record1->set_max_time(0l);
+    record1->set_last_change(131348LL);
+    record1->set_prevent_suspend_time(0LL);
+
+    WakeupSourceProto* record2 = expected.add_wakeup_sources();
+    record2->set_name("ipc000000aa_ATFWD-daemon");
+    record2->set_active_count(143);
+    record2->set_event_count(143);
+    record2->set_wakeup_count(0);
+    record2->set_expire_count(0);
+    record2->set_active_since(0l);
+    record2->set_total_time(123l);
+    record2->set_max_time(3l);
+    record2->set_last_change(2067286206LL);
+    record2->set_prevent_suspend_time(0LL);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
new file mode 100644
index 0000000..de64e70
--- /dev/null
+++ b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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 "PageTypeInfoParser.h"
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PageTypeInfoParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PageTypeInfoParserTest, Success) {
+    const string testFile = kTestDataPath + "pagetypeinfo.txt";
+    PageTypeInfoParser parser;
+    PageTypeInfo expected;
+
+    expected.set_page_block_order(10);
+    expected.set_pages_per_block(1024);
+
+    MigrateTypeProto* mt1 = expected.add_migrate_types();
+    mt1->set_node(0);
+    mt1->set_zone("DMA");
+    mt1->set_type("Unmovable");
+    int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
+    for (auto i=0; i<11; i++) {
+        mt1->add_free_pages_count(arr1[i]);
+    }
+
+    MigrateTypeProto* mt2 = expected.add_migrate_types();
+    mt2->set_node(0);
+    mt2->set_zone("Normal");
+    mt2->set_type("Reclaimable");
+    int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
+    for (auto i=0; i<11; i++) {
+        mt2->add_free_pages_count(arr2[i]);
+    }
+
+    BlockProto* block1 = expected.add_blocks();
+    block1->set_node(0);
+    block1->set_zone("DMA");
+    block1->set_unmovable(74);
+    block1->set_reclaimable(9);
+    block1->set_movable(337);
+    block1->set_cma(41);
+    block1->set_reserve(1);
+    block1->set_isolate(0);
+
+
+    BlockProto* block2 = expected.add_blocks();
+    block2->set_node(0);
+    block2->set_zone("Normal");
+    block2->set_unmovable(70);
+    block2->set_reclaimable(12);
+    block2->set_movable(423);
+    block2->set_cma(0);
+    block2->set_reserve(1);
+    block2->set_isolate(0);
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/ProcrankParser_test.cpp b/cmds/incident_helper/tests/ProcrankParser_test.cpp
new file mode 100644
index 0000000..e86647a
--- /dev/null
+++ b/cmds/incident_helper/tests/ProcrankParser_test.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 "ProcrankParser.h"
+
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class ProcrankParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+    string getSerializedString(::google::protobuf::Message& message) {
+        string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(ProcrankParserTest, HasSwapInfo) {
+    const string testFile = kTestDataPath + "procrank.txt";
+    ProcrankParser parser;
+    Procrank expected;
+
+    ProcessProto* process1 = expected.add_processes();
+    process1->set_pid(1119);
+    process1->set_vss(2607640);
+    process1->set_rss(339564);
+    process1->set_pss(180278);
+    process1->set_uss(114216);
+    process1->set_swap(1584);
+    process1->set_pswap(46);
+    process1->set_uswap(0);
+    process1->set_zswap(10);
+    process1->set_cmdline("system_server");
+
+    ProcessProto* process2 = expected.add_processes();
+    process2->set_pid(649);
+    process2->set_vss(11016);
+    process2->set_rss(1448);
+    process2->set_pss(98);
+    process2->set_uss(48);
+    process2->set_swap(472);
+    process2->set_pswap(342);
+    process2->set_uswap(212);
+    process2->set_zswap(75);
+    process2->set_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_swap(88164);
+    total->set_pswap(31069);
+    total->set_uswap(27612);
+    total->set_zswap(6826);
+    total->set_cmdline("TOTAL");
+
+    expected.mutable_summary()->mutable_zram()
+        ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
+    expected.mutable_summary()->mutable_ram()
+        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
+
+TEST_F(ProcrankParserTest, NoSwapInfo) {
+    const string testFile = kTestDataPath + "procrank_short.txt";
+    ProcrankParser parser;
+    Procrank expected;
+
+    ProcessProto* process1 = expected.add_processes();
+    process1->set_pid(1119);
+    process1->set_vss(2607640);
+    process1->set_rss(339564);
+    process1->set_pss(180278);
+    process1->set_uss(114216);
+    process1->set_cmdline("system_server");
+
+    ProcessProto* process2 = expected.add_processes();
+    process2->set_pid(649);
+    process2->set_vss(11016);
+    process2->set_rss(1448);
+    process2->set_pss(98);
+    process2->set_uss(48);
+    process2->set_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_cmdline("TOTAL");
+
+    expected.mutable_summary()->mutable_ram()
+        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp
index d926ea7..77ae1a7 100644
--- a/cmds/incidentd/src/PrivacyBuffer.cpp
+++ b/cmds/incidentd/src/PrivacyBuffer.cpp
@@ -87,12 +87,12 @@
     // current field is message type and its sub-fields have extra privacy policies
     uint32_t msgSize = mData.readRawVarint();
     EncodedBuffer::Pointer start = mData.rp()->copy();
+    long long token = mProto.start(policy->EncodedFieldId());
     while (mData.rp()->pos() - start.pos() != msgSize) {
-        long long token = mProto.start(policy->EncodedFieldId());
         status_t err = stripField(policy, spec);
         if (err != NO_ERROR) return err;
-        mProto.end(token);
     }
+    mProto.end(token);
     return NO_ERROR;
 }
 
diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h
index da82b00..dfd2312 100644
--- a/cmds/incidentd/src/section_list.h
+++ b/cmds/incidentd/src/section_list.h
@@ -30,7 +30,7 @@
  * This is the mapping of section IDs to each section's privacy policy.
  * The section IDs are guaranteed in ascending order, not NULL-terminated since size is provided.
  */
-extern const Privacy* PRIVACY_POLICY_LIST[];
+extern const Privacy** PRIVACY_POLICY_LIST;
 
 extern const int PRIVACY_POLICY_COUNT;
 
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
index 8f6e355..84a2a82 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -260,3 +260,13 @@
     PrivacySpec spec;
     ASSERT_EQ(privacyBuf.strip(spec), BAD_VALUE);
 }
+
+TEST_F(PrivacyBufferTest, SelfRecursionMessage) {
+    string input = "\x2a\"" + VARINT_FIELD_1 + STRING_FIELD_2 + MESSAGE_FIELD_5;
+    writeToFdBuffer(input);
+    Privacy* field5 = create_message_privacy(5, NULL);
+    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), field5, NULL };
+    field5->children = list;
+    string expected = "\x2a\x1c" + STRING_FIELD_2 + "\x2a\xd" + STRING_FIELD_2;
+    assertStrip(EXPLICIT, expected, field5);
+}
diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp
index e47b61cf..4acc429 100644
--- a/cmds/incidentd/tests/section_list.cpp
+++ b/cmds/incidentd/tests/section_list.cpp
@@ -20,9 +20,11 @@
 Privacy field_0 { 0, 11, list, EXPLICIT, NULL };
 Privacy field_1 { 1, 9, NULL, AUTOMATIC, NULL };
 
-const Privacy* PRIVACY_POLICY_LIST[] = {
+Privacy* final_list[] = {
     &field_0,
     &field_1
 };
 
-const int PRIVACY_POLICY_COUNT = 2;
\ No newline at end of file
+const Privacy** PRIVACY_POLICY_LIST = const_cast<const Privacy**>(final_list);
+
+const int PRIVACY_POLICY_COUNT = 2;
diff --git a/cmds/statsd/.clang-format b/cmds/statsd/.clang-format
index 3d64bee..cead3a0 100644
--- a/cmds/statsd/.clang-format
+++ b/cmds/statsd/.clang-format
@@ -12,3 +12,6 @@
 PointerAlignment: Left
 TabWidth: 4
 AccessModifierOffset: -4
+IncludeCategories:
+  - Regex:    '^"Log\.h"'
+    Priority:    -1
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index db634d4..54ade35 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -42,6 +42,9 @@
     src/metrics/EventMetricProducer.cpp \
     src/metrics/CountMetricProducer.cpp \
     src/metrics/DurationMetricProducer.cpp \
+    src/metrics/duration_helper/OringDurationTracker.cpp \
+    src/metrics/duration_helper/MaxDurationTracker.cpp \
+    src/metrics/ValueMetricProducer.cpp \
     src/metrics/MetricsManager.cpp \
     src/metrics/metrics_manager_util.cpp \
     src/packages/UidMap.cpp \
@@ -103,7 +106,8 @@
 LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
 LOCAL_C_INCLUDES += $(statsd_common_c_includes)
 
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+    libgtest_prod
 
 LOCAL_MODULE_CLASS := EXECUTABLES
 
@@ -142,7 +146,9 @@
     tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
     tests/MetricsManager_test.cpp \
-    tests/UidMap_test.cpp
+    tests/UidMap_test.cpp \
+    tests/OringDurationTracker_test.cpp \
+    tests/MaxDurationTracker_test.cpp
 
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index cdaca1b..c0cedb1 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -60,6 +60,7 @@
 
     unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
     if (newMetricsManager->isConfigValid()) {
+        mUidMap->OnConfigUpdated(key);
         mMetricsManagers[key] = std::move(newMetricsManager);
         // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
         ALOGD("StatsdConfig valid");
@@ -69,14 +70,27 @@
     }
 }
 
-vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+ConfigMetricsReport StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+    ConfigMetricsReport report;
+
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
-        return vector<StatsLogReport>();
+        return report;
     }
 
-    return it->second->onDumpReport();
+    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;
+    }
+    auto temp = mUidMap->getOutput(key);
+    report.set_allocated_uid_map(&temp);
+    return report;
 }
 
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
@@ -84,6 +98,7 @@
     if (it != mMetricsManagers.end()) {
         it->second->finish();
         mMetricsManagers.erase(it);
+        mUidMap->OnConfigRemoved(key);
     }
     auto flushTime = mLastFlushTimes.find(key);
     if (flushTime != mLastFlushTimes.end()) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 6463441..0083827 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -42,7 +42,7 @@
     void OnConfigRemoved(const ConfigKey& key);
 
     // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
-    std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+    ConfigMetricsReport onDumpReport(const ConfigKey& key);
 
     /* Request a flush through a binder call. */
     void flush();
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 604753e..edb1a0f 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -65,7 +65,6 @@
 StatsService::StatsService(const sp<Looper>& handlerLooper)
     : mAnomalyMonitor(new AnomalyMonitor(2))  // TODO: Put this comment somewhere better
 {
-    mStatsPullerManager = new StatsPullerManager();
     mUidMap = new UidMap();
     mConfigManager = new ConfigManager();
     mProcessor = new StatsLogProcessor(mUidMap, [this](const vector<uint8_t>& log) {
@@ -231,6 +230,14 @@
     fprintf(out, "                parameter on eng builds.  If UID is omitted the calling\n");
     fprintf(out, "                uid is used.\n");
     fprintf(out, "  NAME          The per-uid name to use\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats dump-report [UID] NAME\n");
+    fprintf(out, "  Dump all metric data for a configuration.\n");
+    fprintf(out, "  UID           The uid of the configuration. It is only possible to pass\n");
+    fprintf(out, "                the UID parameter on eng builds. If UID is omitted the\n");
+    fprintf(out, "                calling uid is used.\n");
+    fprintf(out, "  NAME          The name of the configuration\n");
 }
 
 status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
@@ -312,7 +319,7 @@
             // Automatically pick the UID
             uid = IPCThreadState::self()->getCallingUid();
             // TODO: What if this isn't a binder call? Should we fail?
-            name.assign(args[2].c_str(), args[2].size());
+            name.assign(args[1].c_str(), args[1].size());
             good = true;
         } else if (argCount == 3) {
             // If it's a userdebug or eng build, then the shell user can
@@ -366,7 +373,7 @@
 
 status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) {
     int s = atoi(args[1].c_str());
-    auto stats = mStatsPullerManager->Pull(s);
+    auto stats = m_stats_puller_manager.Pull(s, time(nullptr));
     for (const auto& it : stats) {
         fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
     }
@@ -433,8 +440,9 @@
                                          "Only system uid can call informPollAlarmFired");
     }
 
+    m_stats_puller_manager.OnAlarmFired();
+
     if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
-    // TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
 
     return Status::ok();
 }
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 7f04658..3930d31 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -27,7 +27,6 @@
 #include <android/os/IStatsCallbacks.h>
 #include <android/os/IStatsCompanionService.h>
 #include <binder/IResultReceiver.h>
-#include <binder/IShellCallback.h>
 #include <utils/Looper.h>
 
 #include <deque>
@@ -158,7 +157,7 @@
     /**
      * Fetches external metrics.
      */
-    sp<StatsPullerManager> mStatsPullerManager;
+    StatsPullerManager& m_stats_puller_manager = StatsPullerManager::GetInstance();
 
     /**
      * Tracks the configurations that have been passed to statsd.
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index f56c15a..953bcb3 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -16,7 +16,6 @@
 
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
-
 #include "CombinationConditionTracker.h"
 
 #include <log/logprint.h>
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index fc88a88..dbdb3b7 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -46,8 +46,6 @@
                         const std::vector<sp<ConditionTracker>>& allConditions,
                         std::vector<ConditionState>& conditionCache) override;
 
-    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
-
 private:
     LogicalOperation mLogicalOperation;
     // Store index of the children Conditions.
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 055b478..bb5ddeb 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "Log.h"
-
 #include "condition/condition_util.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/LogMatchingTracker.h"
@@ -103,8 +101,6 @@
         mSliced = mSliced | sliced;
     }
 
-    virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
-
 protected:
     // We don't really need the string name, but having a name here makes log messages
     // easy to debug.
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 4889b64..1a01afa 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -27,19 +27,23 @@
 // Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
 class ConditionWizard : public virtual android::RefBase {
 public:
+    ConditionWizard(){};  // for testing
     ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
         : mAllConditions(conditionTrackers){};
 
+    virtual ~ConditionWizard(){};
+
     // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
     // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
     //                       condition.
     // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
     // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
-    ConditionState query(const int conditionIndex,
-                         const std::map<std::string, HashableDimensionKey>& conditionParameters);
+    virtual ConditionState query(
+            const int conditionIndex,
+            const std::map<std::string, HashableDimensionKey>& conditionParameters);
 
 private:
-    std::vector<sp<ConditionTracker>>& mAllConditions;
+    std::vector<sp<ConditionTracker>> mAllConditions;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index aff4768..b691faea 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -74,6 +74,13 @@
         mStopAllLogMatcherIndex = -1;
     }
 
+    mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
+                      simpleCondition.dimension().end());
+
+    if (mDimension.size() > 0) {
+        mSliced = true;
+    }
+
     mInitialized = true;
 }
 
@@ -98,12 +105,6 @@
     }
 }
 
-void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
-    VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
-    mDimensionsList.push_back(keyMatchers);
-    mSliced = true;
-}
-
 bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
                                                const vector<MatchingState>& eventMatcherValues,
                                                const vector<sp<ConditionTracker>>& mAllConditions,
@@ -157,18 +158,15 @@
         // TODO: handle stop all; all dimension should be cleared.
     }
 
-    if (mDimensionsList.size() > 0) {
-        for (size_t i = 0; i < mDimensionsList.size(); i++) {
-            const auto& dim = mDimensionsList[i];
-            vector<KeyValuePair> key = getDimensionKey(event, dim);
-            HashableDimensionKey hashableKey = getHashableKey(key);
-            if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
-                mSlicedConditionState[hashableKey] != newCondition) {
-                slicedChanged = true;
-                mSlicedConditionState[hashableKey] = newCondition;
-            }
-            VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+
+    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);
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 1f357f0..b72157b 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -27,8 +27,6 @@
 
 class SimpleConditionTracker : public virtual ConditionTracker {
 public:
-    // dimensions is a vector of vector because for one single condition, different metrics may be
-    // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
     SimpleConditionTracker(const std::string& name, const int index,
                            const SimpleCondition& simpleCondition,
                            const std::unordered_map<std::string, int>& trackerNameIndexMap);
@@ -51,8 +49,6 @@
                         const std::vector<sp<ConditionTracker>>& allConditions,
                         std::vector<ConditionState>& conditionCache) override;
 
-    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
-
 private:
     // The index of the LogEventMatcher which defines the start.
     int mStartLogMatcherIndex;
@@ -66,8 +62,11 @@
     // The index of the LogEventMatcher which defines the stop all.
     int mStopAllLogMatcherIndex;
 
-    // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
-    std::vector<std::vector<KeyMatcher>> mDimensionsList;
+    // 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;
 
     // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
     // that StatsLogReport wants.
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index c16971a..8812719 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -118,9 +118,10 @@
     StatsdConfig config;
     config.set_config_id(12345L);
 
-    int WAKE_LOCK_TAG_ID = 11;
+    int WAKE_LOCK_TAG_ID = 1111;  // put a fake id here to make testing easier.
     int WAKE_LOCK_UID_KEY_ID = 1;
-    int WAKE_LOCK_STATE_KEY = 3;
+    int WAKE_LOCK_NAME_KEY = 3;
+    int WAKE_LOCK_STATE_KEY = 4;
     int WAKE_LOCK_ACQUIRE_VALUE = 1;
     int WAKE_LOCK_RELEASE_VALUE = 0;
 
@@ -138,6 +139,9 @@
     int UID_PROCESS_STATE_TAG_ID = 27;
     int UID_PROCESS_STATE_UID_KEY = 1;
 
+    int KERNEL_WAKELOCK_TAG_ID = 41;
+    int KERNEL_WAKELOCK_NAME_KEY = 4;
+
     // Count Screen ON events.
     CountMetric* metric = config.add_count_metric();
     metric->set_metric_id(1);
@@ -180,24 +184,67 @@
     link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
     link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
 
-    // Duration of an app holding wl, while screen on and app in background
+    // Duration of an app holding any wl, while screen on and app in background, slice by uid
     DurationMetric* durationMetric = config.add_duration_metric();
     durationMetric->set_metric_id(5);
-    durationMetric->set_start("APP_GET_WL");
-    durationMetric->set_stop("APP_RELEASE_WL");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     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_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
     link->set_condition("APP_IS_BACKGROUND");
     link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
     link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
 
+    // max Duration of an app holding any wl, while screen on and app in background, slice by uid
+    durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(6);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    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_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    link = durationMetric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Duration of an app holding any wl, while screen on and app in background
+    durationMetric = config.add_duration_metric();
+    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_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    link = durationMetric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Duration of screen on time.
+    durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(8);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+    durationMetric->set_what("SCREEN_IS_ON");
+
+    // Value metric to count KERNEL_WAKELOCK when screen turned on
+    ValueMetric* valueMetric = config.add_value_metric();
+    valueMetric->set_metric_id(6);
+    valueMetric->set_what("KERNEL_WAKELOCK");
+    valueMetric->set_value_field(1);
+    valueMetric->set_condition("SCREEN_IS_ON");
+    keyMatcher = valueMetric->add_dimension();
+    keyMatcher->set_key(KERNEL_WAKELOCK_NAME_KEY);
+    // This is for testing easier. We should never set bucket size this small.
+    valueMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
+
     // Add an EventMetric to log process state change events.
     EventMetric* eventMetric = config.add_event_metric();
-    eventMetric->set_metric_id(6);
+    eventMetric->set_metric_id(9);
     eventMetric->set_what("SCREEN_TURNED_ON");
 
     // Event matchers............
@@ -272,6 +319,8 @@
     simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("APP_GOES_BACKGROUND");
     simpleCondition->set_stop("APP_GOES_FOREGROUND");
+    KeyMatcher* condition_dimension1 = simpleCondition->add_dimension();
+    condition_dimension1->set_key(APP_USAGE_UID_KEY_ID);
 
     condition = config.add_condition();
     condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
@@ -280,6 +329,16 @@
     combination_condition->add_condition("APP_IS_BACKGROUND");
     combination_condition->add_condition("SCREEN_IS_ON");
 
+    condition = config.add_condition();
+    condition->set_name("WL_STATE_PER_APP_PER_NAME");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("APP_GET_WL");
+    simpleCondition->set_stop("APP_RELEASE_WL");
+    KeyMatcher* condition_dimension = simpleCondition->add_dimension();
+    condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+    condition_dimension = simpleCondition->add_dimension();
+    condition_dimension->set_key(WAKE_LOCK_NAME_KEY);
+
     return config;
 }
 
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.cpp b/cmds/statsd/src/external/KernelWakelockPuller.cpp
index ee072f8..00259a8 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.cpp
+++ b/cmds/statsd/src/external/KernelWakelockPuller.cpp
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
+#define DEBUG true
 #include "Log.h"
 
 #include <android/os/IStatsCompanionService.h>
 #include <binder/IPCThreadState.h>
 #include <private/android_filesystem_config.h>
+#include "KernelWakelockPuller.h"
 #include "StatsService.h"
-#include "external/KernelWakelockPuller.h"
-#include "external/StatsPuller.h"
 
 using namespace android;
 using namespace android::base;
@@ -33,11 +33,11 @@
 namespace os {
 namespace statsd {
 
-const int KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS = 20;
+const int KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS = 1004;
 
 // The reading and parsing are implemented in Java. It is not difficult to port over. But for now
 // let StatsCompanionService handle that and send the data back.
-vector<StatsLogEventWrapper> KernelWakelockPuller::pull() {
+vector<StatsLogEventWrapper> KernelWakelockPuller::Pull() {
     sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
     vector<StatsLogEventWrapper> returned_value;
     if (statsCompanion != NULL) {
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.h b/cmds/statsd/src/external/KernelWakelockPuller.h
index c12806c..cc8059d 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.h
+++ b/cmds/statsd/src/external/KernelWakelockPuller.h
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef STATSD_KERNELWAKELOCKPULLER_H
-#define STATSD_KERNELWAKELOCKPULLER_H
+#pragma once
 
 #include <utils/String16.h>
-#include "external/StatsPuller.h"
+#include "StatsPuller.h"
 
 namespace android {
 namespace os {
@@ -29,11 +28,9 @@
     // a number of stats need to be pulled from StatsCompanionService
     //
     const static int PULL_CODE_KERNEL_WAKELOCKS;
-    vector<StatsLogEventWrapper> pull() override;
+    vector<StatsLogEventWrapper> Pull() override;
 };
 
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
-#endif  // STATSD_KERNELWAKELOCKPULLER_H
diff --git a/cmds/statsd/src/external/PullDataReceiver.h b/cmds/statsd/src/external/PullDataReceiver.h
new file mode 100644
index 0000000..0d505cb
--- /dev/null
+++ b/cmds/statsd/src/external/PullDataReceiver.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <utils/String16.h>
+#include <unordered_map>
+#include <utils/RefBase.h>
+#include "StatsPuller.h"
+#include "logd/LogEvent.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class PullDataReceiver : virtual public RefBase{
+ public:
+  virtual ~PullDataReceiver() {}
+  virtual void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 6655629..774e7f0 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef STATSD_STATSPULLER_H
-#define STATSD_STATSPULLER_H
+#pragma once
 
 #include <android/os/StatsLogEventWrapper.h>
 #include <utils/String16.h>
@@ -32,11 +31,9 @@
 public:
     virtual ~StatsPuller(){};
 
-    virtual vector<StatsLogEventWrapper> pull() = 0;
+    virtual vector<StatsLogEventWrapper> Pull() = 0;
 };
 
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
-#endif  // STATSD_STATSPULLER_H
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 7f554d3..f45cb1c 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -18,44 +18,58 @@
 #include "Log.h"
 
 #include <android/os/IStatsCompanionService.h>
-#include "KernelWakelockPuller.h"
-#include "StatsService.h"
-#include "external/StatsPullerManager.h"
-#include "logd/LogEvent.h"
 #include <cutils/log.h>
 #include <algorithm>
+#include <climits>
+#include "KernelWakelockPuller.h"
+#include "StatsPullerManager.h"
+#include "StatsService.h"
+#include "logd/LogEvent.h"
 
 #include <iostream>
 
-using namespace android;
+using std::string;
+using std::vector;
 
 namespace android {
 namespace os {
 namespace statsd {
 
-const int StatsPullerManager::KERNEL_WAKELOCKS = 1;
+const int kernel_wakelock = 1;
+const unordered_map<string, int> StatsPullerManager::kPullCodes({{"KERNEL_WAKELOCK",
+                                                                  kernel_wakelock}});
 
-StatsPullerManager::StatsPullerManager() {
-    mStatsPullers.insert(
-            {static_cast<int>(KERNEL_WAKELOCKS), std::make_unique<KernelWakelockPuller>()});
+StatsPullerManager::StatsPullerManager()
+    : mCurrentPullingInterval(LONG_MAX), mPullStartTimeMs(get_pull_start_time_ms()) {
+    mPullers.insert({kernel_wakelock, make_unique<KernelWakelockPuller>()});
+    mStatsCompanionService = get_stats_companion_service();
+    if (mStatsCompanionService != nullptr) {
+        mStatsCompanionService->cancelPullingAlarms();
+    } else {
+        VLOG("Failed to update pulling interval");
+    }
 }
 
-vector<std::shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode) {
+static const int log_msg_header_size = 28;
+
+vector<shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode, uint64_t timestampSec) {
     if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
 
-    vector<std::shared_ptr<LogEvent>> ret;
-    if (mStatsPullers.find(pullCode) != mStatsPullers.end()) {
-        vector<StatsLogEventWrapper> outputs = (mStatsPullers.find(pullCode)->second)->pull();
+    vector<shared_ptr<LogEvent>> ret;
+    auto itr = mPullers.find(pullCode);
+    if (itr != mPullers.end()) {
+        vector<StatsLogEventWrapper> outputs = itr->second->Pull();
         for (const StatsLogEventWrapper& it : outputs) {
             log_msg tmp;
+            tmp.entry_v1.sec = timestampSec;
+            tmp.entry_v1.nsec = 0;
             tmp.entry_v1.len = it.bytes.size();
             // Manually set the header size to 28 bytes to match the pushed log events.
-            tmp.entry.hdr_size = 28;
+            tmp.entry.hdr_size = log_msg_header_size;
             // And set the received bytes starting after the 28 bytes reserved for header.
-            std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + 28);
-            std::shared_ptr<LogEvent> evt = std::make_shared<LogEvent>(tmp);
+            copy(it.bytes.begin(), it.bytes.end(), tmp.buf + log_msg_header_size);
+            shared_ptr<LogEvent> evt = make_shared<LogEvent>(tmp);
             ret.push_back(evt);
-            // ret.emplace_back(tmp);
         }
         return ret;
     } else {
@@ -64,6 +78,112 @@
     }
 }
 
+sp<IStatsCompanionService> StatsPullerManager::get_stats_companion_service() {
+    sp<IStatsCompanionService> statsCompanion = nullptr;
+    // Get statscompanion service from service manager
+    const sp<IServiceManager> sm(defaultServiceManager());
+    if (sm != nullptr) {
+        const String16 name("statscompanion");
+        statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
+        if (statsCompanion == nullptr) {
+            ALOGW("statscompanion service unavailable!");
+            return nullptr;
+        }
+    }
+    return statsCompanion;
+}
+
+StatsPullerManager& StatsPullerManager::GetInstance() {
+    static StatsPullerManager instance;
+    return instance;
+}
+
+int StatsPullerManager::GetPullCode(string atomName) {
+    if (kPullCodes.find(atomName) != kPullCodes.end()) {
+        return kPullCodes.find(atomName)->second;
+    } else {
+        return -1;
+    }
+}
+
+long StatsPullerManager::get_pull_start_time_ms() {
+    // TODO: limit and align pull intervals to 10min boundaries if this turns out to be a problem
+    return time(nullptr) * 1000;
+}
+
+void StatsPullerManager::RegisterReceiver(int pullCode, sp<PullDataReceiver> receiver, long intervalMs) {
+    AutoMutex _l(mReceiversLock);
+    vector<ReceiverInfo>& receivers = mReceivers[pullCode];
+    for (auto it = receivers.begin(); it != receivers.end(); it++) {
+        if (it->receiver.get() == receiver.get()) {
+            VLOG("Receiver already registered of %d", (int)receivers.size());
+            return;
+        }
+    }
+    ReceiverInfo receiverInfo;
+    receiverInfo.receiver = receiver;
+    receiverInfo.timeInfo.first = intervalMs;
+    receivers.push_back(receiverInfo);
+
+    // There is only one alarm for all pulled events. So only set it to the smallest denom.
+    if (intervalMs < mCurrentPullingInterval) {
+        VLOG("Updating pulling interval %ld", intervalMs);
+        mCurrentPullingInterval = intervalMs;
+        if (mStatsCompanionService != nullptr) {
+            mStatsCompanionService->setPullingAlarms(mPullStartTimeMs, mCurrentPullingInterval);
+        } else {
+            VLOG("Failed to update pulling interval");
+        }
+    }
+    VLOG("Puller for pullcode %d registered of %d", pullCode, (int)receivers.size());
+}
+
+void StatsPullerManager::UnRegisterReceiver(int pullCode, sp<PullDataReceiver> receiver) {
+    AutoMutex _l(mReceiversLock);
+    if (mReceivers.find(pullCode) == mReceivers.end()) {
+        VLOG("Unknown pull code or no receivers: %d", pullCode);
+        return;
+    }
+    auto& receivers = mReceivers.find(pullCode)->second;
+    for (auto it = receivers.begin(); it != receivers.end(); it++) {
+        if (receiver.get() == it->receiver.get()) {
+            receivers.erase(it);
+            VLOG("Puller for pullcode %d unregistered of %d", pullCode, (int)receivers.size());
+            return;
+        }
+    }
+}
+
+void StatsPullerManager::OnAlarmFired() {
+    AutoMutex _l(mReceiversLock);
+
+    uint64_t currentTimeMs = time(nullptr) * 1000;
+
+    vector<pair<int, vector<ReceiverInfo*>>> needToPull =
+            vector<pair<int, vector<ReceiverInfo*>>>();
+    for (auto& pair : mReceivers) {
+        vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>();
+        if (pair.second.size() != 0){
+            for(auto& receiverInfo : pair.second) {
+                if (receiverInfo.timeInfo.first + receiverInfo.timeInfo.second > currentTimeMs) {
+                    receivers.push_back(&receiverInfo);
+                }
+            }
+            if (receivers.size() > 0) {
+                needToPull.push_back(make_pair(pair.first, receivers));
+            }
+        }
+    }
+
+    for (const auto& pullInfo : needToPull) {
+        const vector<shared_ptr<LogEvent>>& data = Pull(pullInfo.first, currentTimeMs/1000);
+        for(const auto& receiverInfo : pullInfo.second) {
+            receiverInfo->receiver->onDataPulled(data);
+            receiverInfo->timeInfo.second = currentTimeMs;
+        }
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index e46aec1..e599b69 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -14,38 +14,79 @@
  * limitations under the License.
  */
 
-#ifndef STATSD_STATSPULLERMANAGER_H
-#define STATSD_STATSPULLERMANAGER_H
+#pragma once
 
+#include <android/os/IStatsCompanionService.h>
+#include <binder/IServiceManager.h>
+#include <utils/RefBase.h>
 #include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <string>
 #include <unordered_map>
-#include "external/StatsPuller.h"
+#include <vector>
+#include "PullDataReceiver.h"
+#include "StatsPuller.h"
 #include "logd/LogEvent.h"
-#include "matchers/matcher_util.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-const static int KERNEL_WAKELOCKS = 1;
-
 class StatsPullerManager : public virtual RefBase {
 public:
-    // Enums of pulled data types (pullCodes)
-    // These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
-    // TODO: pull the constant from stats_events.proto instead
-    const static int KERNEL_WAKELOCKS;
-    StatsPullerManager();
+    static StatsPullerManager& GetInstance();
+
+    void RegisterReceiver(int pullCode, sp<PullDataReceiver> receiver, long intervalMs);
+
+    void UnRegisterReceiver(int pullCode, sp<PullDataReceiver> receiver);
 
     // We return a vector of shared_ptr since LogEvent's copy constructor is not available.
-    vector<std::shared_ptr<LogEvent>> Pull(const int pullCode);
+    vector<std::shared_ptr<LogEvent>> Pull(const int pullCode, const uint64_t timestampSec);
+
+    // Translate metric name to pullCodes.
+    // return -1 if no valid pullCode is found
+    int GetPullCode(std::string metricName);
+
+    void OnAlarmFired();
 
 private:
-    std::unordered_map<int, std::unique_ptr<StatsPuller>> mStatsPullers;
+    StatsPullerManager();
+
+    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+    sp<IStatsCompanionService> get_stats_companion_service();
+
+    std::unordered_map<int, std::unique_ptr<StatsPuller>> mPullers;
+
+
+
+      // internal state of a bucket.
+      typedef struct {
+        // pull_interval_sec : last_pull_time_sec
+        std::pair<uint64_t, uint64_t> timeInfo;
+        sp<PullDataReceiver> receiver;
+      } ReceiverInfo;
+
+    std::map<int, std::vector<ReceiverInfo>> mReceivers;
+
+    Mutex mReceiversLock;
+
+    long mCurrentPullingInterval;
+
+    // for value metrics, it is important for the buckets to be aligned to multiple of smallest
+    // bucket size. All pulled metrics start pulling based on this time, so that they can be
+    // correctly attributed to the correct buckets. Pulled data attach a timestamp which is the
+    // request time.
+    const long mPullStartTimeMs;
+
+    long get_pull_start_time_ms();
+
+    LogEvent parse_pulled_data(String16 data);
+
+    static const std::unordered_map<std::string, int> kPullCodes;
 };
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
-
-#endif  // STATSD_STATSPULLERMANAGER_H
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 1a039f6..26a82cb 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define DEBUG true  // STOPSHIP if true
 #include "logd/LogEvent.h"
 
 #include <sstream>
@@ -29,7 +30,7 @@
 
 // We need to keep a copy of the android_log_event_list owned by this instance so that the char*
 // for strings is not cleared before we can read them.
-LogEvent::LogEvent(log_msg msg) : mList(msg) {
+LogEvent::LogEvent(log_msg& msg) : mList(msg) {
     init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
 }
 
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 9ef20ea..df75d9f 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -42,7 +42,7 @@
     /**
      * Read a LogEvent from a log_msg.
      */
-    explicit LogEvent(log_msg msg);
+    explicit LogEvent(log_msg& msg);
 
     /**
      * Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 69f336f..9f8558d 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -21,7 +21,6 @@
 #include "CountMetricProducer.h"
 #include "stats_util.h"
 
-#include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
@@ -94,7 +93,7 @@
     }
 }
 
-void CountMetricProducer::onSlicedConditionMayChange() {
+void CountMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
 }
 
@@ -128,7 +127,7 @@
     // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
 }
 
-void CountMetricProducer::onConditionChanged(const bool conditionMet) {
+void CountMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
 }
@@ -136,7 +135,7 @@
 void CountMetricProducer::onMatchedLogEventInternal(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const map<string, HashableDimensionKey>& conditionKey, bool condition,
-        const LogEvent& event) {
+        const LogEvent& event, bool scheduledPull) {
     uint64_t eventTimeNs = event.GetTimestampNs();
 
     flushCounterIfNeeded(eventTimeNs);
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index be77e47..80e80d9 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -41,23 +41,26 @@
 
     virtual ~CountMetricProducer();
 
-    void onConditionChanged(const bool conditionMet) override;
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
 
     StatsLogReport onDumpReport() override;
 
-    void onSlicedConditionMayChange() override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
 protected:
     void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
                                    const std::map<std::string, HashableDimensionKey>& conditionKey,
-                                   bool condition, const LogEvent& event) override;
+                                   bool condition, const LogEvent& event,
+                                   bool scheduledPull) override;
 
 private:
     const CountMetric mMetric;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index a590bc8..340f503 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -15,11 +15,11 @@
  */
 
 #define DEBUG true
-#include "DurationMetricProducer.h"
+
 #include "Log.h"
+#include "DurationMetricProducer.h"
 #include "stats_util.h"
 
-#include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
@@ -34,13 +34,15 @@
 DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
-                                               const sp<ConditionWizard>& wizard)
+                                               const sp<ConditionWizard>& wizard,
+                                               const vector<KeyMatcher>& internalDimension)
     // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
     : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
       mMetric(metric),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
-      mStopAllIndex(stopAllIndex) {
+      mStopAllIndex(stopAllIndex),
+      mInternalDimension(internalDimension) {
     // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
     // them in the base class, because the proto generated CountMetric, and DurationMetric are
     // not related. Maybe we should add a template in the future??
@@ -67,34 +69,43 @@
     VLOG("~DurationMetric() called");
 }
 
+unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
+        vector<DurationBucketInfo>& bucket) {
+    switch (mMetric.type()) {
+        case DurationMetric_AggregationType_DURATION_SUM:
+            return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex,
+                                                     mCurrentBucketStartTimeNs, mBucketSizeNs,
+                                                     bucket);
+        case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+            return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex,
+                                                   mCurrentBucketStartTimeNs, mBucketSizeNs,
+                                                   bucket);
+    }
+}
+
 void DurationMetricProducer::finish() {
     // TODO: write the StatsLogReport to dropbox using
     // DropboxWriter.
 }
 
-void DurationMetricProducer::onSlicedConditionMayChange() {
+void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
     // Now for each of the on-going event, check if the condition has changed for them.
+    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
-        VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
-             pair.second.state);
-        if (pair.second.state == kStopped) {
-            continue;
-        }
-        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
-                            ConditionState::kTrue;
-        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
-        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+        pair.second->onSlicedConditionMayChange(eventTime);
     }
 }
 
-void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+void DurationMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
     // TODO: need to populate the condition change time from the event which triggers the condition
     // change, instead of using current time.
+
+    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
-        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+        pair.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
@@ -107,7 +118,7 @@
     }
     for (const auto& bucket : buckets) {
         data->add_bucket_info()->CopyFrom(bucket);
-        VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+        VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(),
              bucket.end_bucket_nanos(), bucket.duration_nanos());
     }
 }
@@ -120,7 +131,7 @@
     // Dump current bucket if it's stale.
     // If current bucket is still on-going, don't force dump current bucket.
     // In finish(), We can force dump current bucket.
-    flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    flushIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
     report.set_end_report_nanos(mCurrentBucketStartTimeNs);
 
     StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
@@ -137,216 +148,50 @@
     return report;
 };
 
-void DurationMetricProducer::onMatchedLogEventInternal(
-        const size_t matcherIndex, const HashableDimensionKey& eventKey,
-        const map<string, HashableDimensionKey>& conditionKeys, bool condition,
-        const LogEvent& event) {
-    flushDurationIfNeeded(event.GetTimestampNs());
-
-    if (matcherIndex == mStopAllIndex) {
-        noteStopAll(event.GetTimestampNs());
-        return;
-    }
-
-    if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end() && mConditionSliced) {
-        // add the durationInfo for the current bucket.
-        auto& durationInfo = mCurrentSlicedDuration[eventKey];
-        durationInfo.conditionKeys = conditionKeys;
-    }
-
-    if (matcherIndex == mStartIndex) {
-        VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), eventKey.c_str(),
-             condition);
-        noteStart(eventKey, condition, event.GetTimestampNs());
-    } else if (matcherIndex == mStopIndex) {
-        VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), eventKey.c_str(),
-             condition);
-        noteStop(eventKey, event.GetTimestampNs());
-    }
-}
-
-void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
-                                                  const bool conditionMet,
-                                                  const uint64_t eventTime) {
-    flushDurationIfNeeded(eventTime);
-
-    auto it = mCurrentSlicedDuration.find(key);
-    if (it == mCurrentSlicedDuration.end()) {
-        return;
-    }
-
-    switch (it->second.state) {
-        case kStarted:
-            // if condition becomes false, kStarted -> kPaused. Record the current duration.
-            if (!conditionMet) {
-                it->second.state = DurationState::kPaused;
-                it->second.lastDuration =
-                        updateDuration(it->second.lastDuration,
-                                       eventTime - it->second.lastStartTime, mMetric.type());
-                VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
-                     key.c_str());
-            }
-            break;
-        case kStopped:
-            // nothing to do if it's stopped.
-            break;
-        case kPaused:
-            // if condition becomes true, kPaused -> kStarted. and the start time is the condition
-            // change time.
-            if (conditionMet) {
-                it->second.state = DurationState::kStarted;
-                it->second.lastStartTime = eventTime;
-                VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
-            }
-            break;
-    }
-}
-
-void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
-                                       const uint64_t eventTime) {
-    // this will add an empty bucket for this key if it didn't exist before.
-    DurationInfo& duration = mCurrentSlicedDuration[key];
-
-    switch (duration.state) {
-        case kStarted:
-            // It's safe to do nothing here. even if condition is not true, it means we are about
-            // to receive the condition change event.
-            break;
-        case kPaused:
-            // Safe to do nothing here. kPaused is waiting for the condition change.
-            break;
-        case kStopped:
-            if (!conditionMet) {
-                // event started, but we need to wait for the condition to become true.
-                duration.state = DurationState::kPaused;
-                break;
-            }
-            duration.state = DurationState::kStarted;
-            duration.lastStartTime = eventTime;
-            break;
-    }
-}
-
-void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
-    if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
-        // we didn't see a start event before. do nothing.
-        return;
-    }
-    DurationInfo& duration = mCurrentSlicedDuration[key];
-
-    switch (duration.state) {
-        case DurationState::kStopped:
-            // already stopped, do nothing.
-            break;
-        case DurationState::kStarted: {
-            duration.state = DurationState::kStopped;
-            int64_t durationTime = eventTime - duration.lastStartTime;
-            VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
-                 (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
-            duration.lastDuration =
-                    updateDuration(duration.lastDuration, durationTime, mMetric.type());
-            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
-            break;
-        }
-        case DurationState::kPaused: {
-            duration.state = DurationState::kStopped;
-            break;
-        }
-    }
-}
-
-int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
-                                               const int64_t durationTime,
-                                               const DurationMetric_AggregationType type) {
-    int64_t result = lastDuration;
-    switch (type) {
-        case DurationMetric_AggregationType_DURATION_SUM:
-            result += durationTime;
-            break;
-        case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
-            if (lastDuration < durationTime) {
-                result = durationTime;
-            }
-            break;
-        case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
-            if (lastDuration > durationTime) {
-                result = durationTime;
-            }
-            break;
-    }
-    return result;
-}
-
-void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
-    for (auto& duration : mCurrentSlicedDuration) {
-        noteStop(duration.first, eventTime);
-    }
-}
-
-// When a new matched event comes in, we check if event falls into the current
-// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
-void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
         return;
     }
 
-    // adjust the bucket start time
-    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
-    DurationBucketInfo info;
-    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
-    info.set_end_bucket_nanos(endTime);
-
-    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
-    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-    VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
-         (long long)mCurrentBucketStartTimeNs);
-
+    VLOG("flushing...........");
     for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
-        int64_t finalDuration = it->second.lastDuration;
-        if (it->second.state == kStarted) {
-            // the event is still on-going, duration needs to be updated.
-            int64_t durationTime = endTime - it->second.lastStartTime;
-            finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
-        }
-
-        VLOG("  final duration for last bucket: %lld", (long long)finalDuration);
-
-        // Don't record empty bucket.
-        if (finalDuration != 0) {
-            info.set_duration_nanos(finalDuration);
-            // it will auto create new vector of CountbucketInfo if the key is not found.
-            auto& bucketList = mPastBuckets[it->first];
-            bucketList.push_back(info);
-        }
-
-        // if the event is still on-going, add the buckets between previous bucket and now. Because
-        // the event has been going on across all the buckets in between.
-        // |prev_bucket|...|..|...|now_bucket|
-        if (it->second.state == kStarted) {
-            for (int i = 1; i < numBucketsForward; i++) {
-                DurationBucketInfo info;
-                info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
-                info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
-                info.set_duration_nanos(mBucketSizeNs);
-                auto& bucketList = mPastBuckets[it->first];
-                bucketList.push_back(info);
-                VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
-            }
-        }
-
-        if (it->second.state == DurationState::kStopped) {
-            // No need to keep buckets for events that were stopped before. If the event starts
-            // again, we will add it back.
+        if (it->second->flushIfNeeded(eventTime)) {
+            VLOG("erase bucket for key %s", it->first.c_str());
             mCurrentSlicedDuration.erase(it);
-        } else {
-            // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
-            // and duration.
-            it->second.lastStartTime = mCurrentBucketStartTimeNs;
-            it->second.lastDuration = 0;
         }
     }
+
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+}
+
+void DurationMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+        const LogEvent& event, bool scheduledPull) {
+    flushIfNeeded(event.GetTimestampNs());
+
+    if (matcherIndex == mStopAllIndex) {
+        for (auto& pair : mCurrentSlicedDuration) {
+            pair.second->noteStopAll(event.GetTimestampNs());
+        }
+        return;
+    }
+
+    HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+
+    if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
+        mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
+    }
+
+    auto it = mCurrentSlicedDuration.find(eventKey);
+
+    if (matcherIndex == mStartIndex) {
+        it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
+
+    } else if (matcherIndex == mStopIndex) {
+        it->second->noteStop(atomKey, event.GetTimestampNs());
+    }
 }
 
 size_t DurationMetricProducer::byteSize() {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 8820403..febf25d 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -22,6 +22,9 @@
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
 #include "MetricProducer.h"
+#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"
@@ -32,52 +35,35 @@
 namespace os {
 namespace statsd {
 
-enum DurationState {
-    kStopped = 0,  // The event is stopped.
-    kStarted = 1,  // The event is on going.
-    kPaused = 2,   // The event is started, but condition is false, clock is paused. When condition
-                   // turns to true, kPaused will become kStarted.
-};
-
-// Hold duration information for current on-going bucket.
-struct DurationInfo {
-    DurationState state;
-    // most recent start time.
-    int64_t lastStartTime;
-    // existing duration in current bucket. Eventually, the duration will be aggregated in
-    // the way specified in AggregateType (Sum, Max, or Min).
-    int64_t lastDuration;
-    // cache the HashableDimensionKeys we need to query the condition for this duration event.
-    std::map<string, HashableDimensionKey> conditionKeys;
-
-    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
-};
-
 class DurationMetricProducer : public MetricProducer {
 public:
     DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
                            const size_t startIndex, const size_t stopIndex,
-                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard,
+                           const vector<KeyMatcher>& internalDimension);
 
     virtual ~DurationMetricProducer();
 
-    void onConditionChanged(const bool conditionMet) override;
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
 
     StatsLogReport onDumpReport() override;
 
-    void onSlicedConditionMayChange() override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
 protected:
     void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
                                    const std::map<std::string, HashableDimensionKey>& conditionKeys,
-                                   bool condition, const LogEvent& event) override;
+                                   bool condition, const LogEvent& event,
+                                   bool scheduledPull) override;
 
 private:
     const DurationMetric mMetric;
@@ -91,26 +77,21 @@
     // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
     const size_t mStopAllIndex;
 
+    // The dimension from the atom predicate. e.g., uid, wakelock name.
+    const vector<KeyMatcher> mInternalDimension;
+
     // Save the past buckets and we can clear when the StatsLogReport is dumped.
     std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
 
     // The current bucket.
-    std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+    std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
+            mCurrentSlicedDuration;
 
     void flushDurationIfNeeded(const uint64_t newEventTime);
 
-    void noteStart(const HashableDimensionKey& key, const bool conditionMet,
-                   const uint64_t eventTime);
+    std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucketInfo>& bucket);
 
-    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
-
-    void noteStopAll(const uint64_t eventTime);
-
-    static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
-                                  const DurationMetric_AggregationType type);
-
-    void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
-                              const uint64_t eventTime);
+    void flushIfNeeded(uint64_t timestamp);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 7e06105..d714179 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -20,7 +20,6 @@
 #include "EventMetricProducer.h"
 #include "stats_util.h"
 
-#include <cutils/log.h>
 #include <limits.h>
 #include <stdlib.h>
 
@@ -78,7 +77,7 @@
 void EventMetricProducer::finish() {
 }
 
-void EventMetricProducer::onSlicedConditionMayChange() {
+void EventMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
 }
 
 StatsLogReport EventMetricProducer::onDumpReport() {
@@ -105,7 +104,7 @@
     return StatsLogReport();
 }
 
-void EventMetricProducer::onConditionChanged(const bool conditionMet) {
+void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
     mCondition = conditionMet;
 }
@@ -113,7 +112,7 @@
 void EventMetricProducer::onMatchedLogEventInternal(
         const size_t matcherIndex, const HashableDimensionKey& eventKey,
         const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
-        const LogEvent& event) {
+        const LogEvent& event, bool scheduledPull) {
 
     if (!condition) {
         return;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 14fa31c..7dd0e38 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -41,20 +41,22 @@
 
     void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
                                    const std::map<std::string, HashableDimensionKey>& conditionKey,
-                                   bool condition, const LogEvent& event) override;
+                                   bool condition, const LogEvent& event, bool scheduledPull) override;
 
-    void onConditionChanged(const bool conditionMet) override;
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
 
     StatsLogReport onDumpReport() override;
 
-    void onSlicedConditionMayChange() override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     size_t byteSize() override;
 
     // TODO: Implement this later.
     virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
 
 private:
     const EventMetric mMetric;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 3c8ce6e..535f4a2 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -21,7 +21,8 @@
 
 using std::map;
 
-void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event,
+                                       bool scheduledPull) {
     uint64_t eventTimeNs = event.GetTimestampNs();
     // this is old event, maybe statsd restarted?
     if (eventTimeNs < mStartTimeNs) {
@@ -59,7 +60,8 @@
         condition = mCondition;
     }
 
-    onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event);
+    onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event,
+                              scheduledPull);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 80eb527..3b117ec 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -48,11 +48,11 @@
     virtual ~MetricProducer(){};
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
-    void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event);
+    void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event, bool scheduledPull);
 
-    virtual void onConditionChanged(const bool condition) = 0;
+    virtual void onConditionChanged(const bool condition, const uint64_t eventTime) = 0;
 
-    virtual void onSlicedConditionMayChange() = 0;
+    virtual void onSlicedConditionMayChange(const uint64_t eventTime) = 0;
 
     // This is called when the metric collecting is done, e.g., when there is a new configuration
     // coming. MetricProducer should do the clean up, and dump existing data to dropbox.
@@ -107,7 +107,7 @@
     virtual void onMatchedLogEventInternal(
             const size_t matcherIndex, const HashableDimensionKey& eventKey,
             const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
-            const LogEvent& event) = 0;
+            const LogEvent& event, bool scheduledPull) = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 4fa3965..521fcc3 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -74,6 +74,7 @@
     }
 
     int tagId = event.GetTagId();
+    uint64_t eventTime = event.GetTimestampNs();
     if (mTagIds.find(tagId) == mTagIds.end()) {
         // not interesting...
         return;
@@ -124,13 +125,14 @@
                 // metric cares about non sliced condition, and it's changed.
                 // Push the new condition to it directly.
                 if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
-                    mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+                    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]) {
-                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
+                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(eventTime);
                 }
             }
         }
@@ -143,7 +145,8 @@
             if (pair != mTrackerToMetricMap.end()) {
                 auto& metricList = pair->second;
                 for (const int metricIndex : metricList) {
-                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
+                    // pushed metrics are never scheduled pulls
+                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event, false);
                 }
             }
         }
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
new file mode 100644
index 0000000..cb6166d
--- /dev/null
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -0,0 +1,246 @@
+/*
+ * 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 "ValueMetricProducer.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::map;
+using std::unordered_map;
+using std::list;
+using std::make_shared;
+using std::shared_ptr;
+using std::unique_ptr;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
+ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard)
+    : MetricProducer((time(nullptr) / 600 * 600 * NANO_SECONDS_IN_A_SECOND), conditionIndex,
+                     wizard),
+      mMetric(metric),
+      mPullCode(mStatsPullerManager.GetPullCode(mMetric.what())) {
+  // TODO: valuemetric for pushed events may need unlimited bucket length
+  mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
+
+  mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+  if (metric.links().size() > 0) {
+    mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                           metric.links().end());
+    mConditionSliced = true;
+  }
+
+  if (!metric.has_condition() && mPullCode != -1) {
+    mStatsPullerManager.RegisterReceiver(mPullCode, this, metric.bucket().bucket_size_millis());
+  }
+
+  VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+       (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+ValueMetricProducer::~ValueMetricProducer() {
+  VLOG("~ValueMetricProducer() called");
+}
+
+void ValueMetricProducer::finish() {
+  // TODO: write the StatsLogReport to dropbox using
+  // DropboxWriter.
+}
+
+static void addSlicedCounterToReport(StatsLogReport_ValueMetricDataWrapper& wrapper,
+                                     const vector<KeyValuePair>& key,
+                                     const vector<ValueBucketInfo>& buckets) {
+  ValueMetricData* data = wrapper.add_data();
+  for (const auto& kv : key) {
+    data->add_dimension()->CopyFrom(kv);
+  }
+  for (const auto& bucket : buckets) {
+    data->add_bucket_info()->CopyFrom(bucket);
+    VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
+         bucket.end_bucket_nanos(), bucket.value());
+  }
+}
+
+void ValueMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+StatsLogReport ValueMetricProducer::onDumpReport() {
+  VLOG("metric %lld dump report now...", mMetric.metric_id());
+
+  StatsLogReport report;
+  report.set_metric_id(mMetric.metric_id());
+  report.set_start_report_nanos(mStartTimeNs);
+
+  // Dump current bucket if it's stale.
+  // If current bucket is still on-going, don't force dump current bucket.
+  // In finish(), We can force dump current bucket.
+  //    flush_if_needed(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+  report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+  StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
+
+  for (const auto& pair : mPastBuckets) {
+    const HashableDimensionKey& hashableKey = pair.first;
+    auto it = mDimensionKeyMap.find(hashableKey);
+    if (it == mDimensionKeyMap.end()) {
+      ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+      continue;
+    }
+
+    VLOG("  dimension key %s", hashableKey.c_str());
+    addSlicedCounterToReport(*wrapper, it->second, pair.second);
+  }
+  return report;
+  // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+}
+
+void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
+    mCondition = condition;
+
+    if (mPullCode != -1) {
+        vector<shared_ptr<LogEvent>> allData = mStatsPullerManager.Pull(mPullCode, eventTime);
+        if (mCondition == true) {
+            mStatsPullerManager.RegisterReceiver(mPullCode, this,
+                                                 mMetric.bucket().bucket_size_millis());
+        } else if (mCondition == ConditionState::kFalse) {
+            mStatsPullerManager.UnRegisterReceiver(mPullCode, this);
+        }
+        if (allData.size() == 0) {
+            return;
+        }
+        AutoMutex _l(mLock);
+        if (allData.size() == 0) {
+            return;
+        }
+        for (const auto& data : allData) {
+            onMatchedLogEvent(0, *data, false);
+        }
+        flush_if_needed(eventTime);
+    }
+    return;
+}
+
+void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
+    if (mCondition == ConditionState::kTrue || !mMetric.has_condition()) {
+        AutoMutex _l(mLock);
+        if (allData.size() == 0) {
+            return;
+        }
+        uint64_t eventTime = allData.at(0)->GetTimestampNs();
+        for (const auto& data : allData) {
+            onMatchedLogEvent(0, *data, true);
+        }
+        flush_if_needed(eventTime);
+    }
+}
+
+void ValueMetricProducer::onMatchedLogEventInternal(
+    const size_t matcherIndex, const HashableDimensionKey& eventKey,
+    const map<string, HashableDimensionKey>& conditionKey, bool condition,
+    const LogEvent& event, bool scheduledPull) {
+  uint64_t eventTimeNs = event.GetTimestampNs();
+  if (eventTimeNs < mCurrentBucketStartTimeNs) {
+      VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+           (long long)mCurrentBucketStartTimeNs);
+      return;
+  }
+
+  Interval& interval = mCurrentSlicedBucket[eventKey];
+
+  long value = get_value(event);
+
+  if (scheduledPull) {
+    if (interval.raw.size() > 0) {
+      interval.raw.back().second = value;
+    } else {
+      interval.raw.push_back(std::make_pair(value, value));
+    }
+    mNextSlicedBucket[eventKey].raw[0].first = value;
+  } else {
+    if (mCondition == ConditionState::kTrue) {
+      interval.raw.push_back(std::make_pair(value, 0));
+    } else {
+      if (interval.raw.size() != 0) {
+        interval.raw.back().second = value;
+      }
+    }
+  }
+  if (mPullCode == -1) {
+      flush_if_needed(eventTimeNs);
+  }
+}
+
+long ValueMetricProducer::get_value(const LogEvent& event) {
+  status_t err = NO_ERROR;
+  long val = event.GetLong(mMetric.value_field(), &err);
+  if (err == NO_ERROR) {
+    return val;
+  } else {
+    VLOG("Can't find value in message.");
+    return 0;
+  }
+}
+
+void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+        VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
+             (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+        return;
+    }
+
+    VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
+         (int)mCurrentSlicedBucket.size());
+    ValueBucketInfo info;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+
+    for (const auto& slice : mCurrentSlicedBucket) {
+      long value = 0;
+      for (const auto& pair : slice.second.raw) {
+        value += pair.second - pair.first;
+      }
+      info.set_value(value);
+      VLOG(" %s, %ld", slice.first.c_str(), value);
+      // it will auto create new vector of ValuebucketInfo if the key is not found.
+      auto& bucketList = mPastBuckets[slice.first];
+      bucketList.push_back(info);
+    }
+
+    // Reset counters
+    mCurrentSlicedBucket.swap(mNextSlicedBucket);
+    mNextSlicedBucket.clear();
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    if (numBucketsForward >1) {
+        VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
+    }
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
new file mode 100644
index 0000000..4f17913
--- /dev/null
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -0,0 +1,92 @@
+/*
+ * 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 <utils/threads.h>
+#include <list>
+#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 {
+namespace os {
+namespace statsd {
+
+class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+public:
+    ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
+                        const sp<ConditionWizard>& wizard);
+
+    virtual ~ValueMetricProducer();
+
+    void onConditionChanged(const bool condition, const uint64_t eventTime) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    void onSlicedConditionMayChange(const uint64_t eventTime);
+
+    void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+    // TODO: Implement this later.
+    size_t byteSize() override{return 0;};
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+protected:
+    void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+                                   const std::map<std::string, HashableDimensionKey>& conditionKey,
+                                   bool condition, const LogEvent& event,
+                                   bool scheduledPull) override;
+
+private:
+    const ValueMetric mMetric;
+
+    StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+
+    Mutex mLock;
+
+    const int mPullCode;
+
+    // internal state of a bucket.
+    typedef struct {
+        std::vector<std::pair<long, long>> raw;
+    } Interval;
+
+    std::unordered_map<HashableDimensionKey, Interval> mCurrentSlicedBucket;
+    // If condition is true and pulling on schedule, the previous bucket value needs to be carried
+    // over to the next bucket.
+    std::unordered_map<HashableDimensionKey, Interval> mNextSlicedBucket;
+
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<ValueBucketInfo>> mPastBuckets;
+
+    long get_value(const LogEvent& event);
+
+    void flush_if_needed(const uint64_t eventTimeNs);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
new file mode 100644
index 0000000..0d0d9a4
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -0,0 +1,89 @@
+/*
+ * 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 DURATION_TRACKER_H
+#define DURATION_TRACKER_H
+
+#include "condition/ConditionWizard.h"
+#include "stats_util.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+    kStopped = 0,  // The event is stopped.
+    kStarted = 1,  // The event is on going.
+    kPaused = 2,   // The event is started, but condition is false, clock is paused. When condition
+                   // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for one atom level duration in current on-going bucket.
+struct DurationInfo {
+    DurationState state;
+    // most recent start time.
+    int64_t lastStartTime;
+    // existing duration in current bucket.
+    int64_t lastDuration;
+    // TODO: Optimize the way we track sliced condition in duration metrics.
+    // cache the HashableDimensionKeys we need to query the condition for this duration event.
+    ConditionKey conditionKeys;
+
+    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationTracker {
+public:
+    DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, uint64_t currentBucketStartNs,
+                    uint64_t bucketSizeNs, std::vector<DurationBucketInfo>& bucket)
+        : mWizard(wizard),
+          mConditionTrackerIndex(conditionIndex),
+          mCurrentBucketStartTimeNs(currentBucketStartNs),
+          mBucketSizeNs(bucketSizeNs),
+          mBucket(bucket),
+          mDuration(0){};
+    virtual ~DurationTracker(){};
+    virtual void noteStart(const HashableDimensionKey& key, bool condition,
+                           const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
+    virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) = 0;
+    virtual void noteStopAll(const uint64_t eventTime) = 0;
+    virtual void onSlicedConditionMayChange(const uint64_t timestamp) = 0;
+    virtual void onConditionChanged(bool condition, const uint64_t timestamp) = 0;
+    // Flush stale buckets if needed, and return true if the tracker has no on-going duration
+    // events, so that the owner can safely remove the tracker.
+    virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
+
+protected:
+    sp<ConditionWizard> mWizard;
+
+    int mConditionTrackerIndex;
+
+    uint64_t mCurrentBucketStartTimeNs;
+
+    int64_t mBucketSizeNs;
+
+    std::vector<DurationBucketInfo>& mBucket;  // where to write output
+
+    int64_t mDuration;  // current recorded duration result
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // DURATION_TRACKER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
new file mode 100644
index 0000000..856ca9d
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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
+
+#include "Log.h"
+#include "MaxDurationTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                       std::vector<DurationBucketInfo>& bucket)
+    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket) {
+}
+
+void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
+                                   const uint64_t eventTime, const ConditionKey& conditionKey) {
+    // this will construct a new DurationInfo if this key didn't exist.
+    DurationInfo& duration = mInfos[key];
+    duration.conditionKeys = conditionKey;
+    VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
+
+    switch (duration.state) {
+        case kStarted:
+            // The same event is already started. Because we are not counting nesting, so ignore.
+            break;
+        case kPaused:
+            // Safe to do nothing here. Paused means started but condition is false.
+            break;
+        case kStopped:
+            if (!condition) {
+                // event started, but we need to wait for the condition to become true.
+                duration.state = DurationState::kPaused;
+            } else {
+                duration.state = DurationState::kStarted;
+                duration.lastStartTime = eventTime;
+            }
+            break;
+    }
+}
+
+void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+    VLOG("MaxDuration: key %s stop", key.c_str());
+    if (mInfos.find(key) == mInfos.end()) {
+        // we didn't see a start event before. do nothing.
+        return;
+    }
+    DurationInfo& duration = mInfos[key];
+
+    switch (duration.state) {
+        case DurationState::kStopped:
+            // already stopped, do nothing.
+            break;
+        case DurationState::kStarted: {
+            duration.state = DurationState::kStopped;
+            int64_t durationTime = eventTime - duration.lastStartTime;
+            VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(), (long long)duration.lastStartTime,
+                 (long long)eventTime, (long long)durationTime);
+            duration.lastDuration = duration.lastDuration + durationTime;
+            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            break;
+        }
+        case DurationState::kPaused: {
+            duration.state = DurationState::kStopped;
+            break;
+        }
+    }
+
+    if (duration.lastDuration > mDuration) {
+        mDuration = duration.lastDuration;
+        VLOG("Max: new max duration: %lld", (long long)mDuration);
+    }
+    // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
+    // same name, they are still considered as different atom durations.
+    mInfos.erase(key);
+}
+void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
+    for (auto& pair : mInfos) {
+        noteStop(pair.first, eventTime);
+    }
+}
+
+bool MaxDurationTracker::flushIfNeeded(uint64_t eventTime) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+        return false;
+    }
+
+    VLOG("MaxDurationTracker flushing.....");
+
+    // adjust the bucket start time
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    DurationBucketInfo info;
+    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(endTime);
+
+    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+
+    bool hasOnGoingStartedEvent = false;  // a kStarted event last across bucket boundaries.
+    bool hasPendingEvent =
+            false;  // has either a kStarted or kPaused event across bucket boundaries
+                    // meaning we need to carry them over to the new bucket.
+    for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
+        int64_t finalDuration = it->second.lastDuration;
+        if (it->second.state == kStarted) {
+            // the event is still on-going, duration needs to be updated.
+            // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
+            // duration between lastStartTime and bucketEnd.
+            int64_t durationTime = endTime - it->second.lastStartTime;
+
+            finalDuration += durationTime;
+            VLOG("  unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
+            // if the event is still on-going, we need to fill the buckets between prev_bucket and
+            // now_bucket. |prev_bucket|...|..|...|now_bucket|
+            hasOnGoingStartedEvent = true;
+        }
+
+        if (finalDuration > mDuration) {
+            mDuration = finalDuration;
+        }
+
+        if (it->second.state == DurationState::kStopped) {
+            // No need to keep buckets for events that were stopped before.
+            mInfos.erase(it);
+        } else {
+            hasPendingEvent = true;
+            // for kPaused, and kStarted event, we will keep track of them, and reset the start time
+            // and duration.
+            it->second.lastStartTime = mCurrentBucketStartTimeNs;
+            it->second.lastDuration = 0;
+        }
+    }
+
+    if (mDuration != 0) {
+        info.set_duration_nanos(mDuration);
+        mBucket.push_back(info);
+        VLOG("  final duration for last bucket: %lld", (long long)mDuration);
+    }
+
+    mDuration = 0;
+    if (hasOnGoingStartedEvent) {
+        for (int i = 1; i < numBucketsForward; i++) {
+            DurationBucketInfo info;
+            info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+            info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+            info.set_duration_nanos(mBucketSizeNs);
+            mBucket.push_back(info);
+            VLOG("  filling gap bucket with duration %lld", (long long)mBucketSizeNs);
+        }
+    }
+    // If this tracker has no pending events, tell owner to remove.
+    return !hasPendingEvent;
+}
+
+void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+    //  VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+    // Now for each of the on-going event, check if the condition has changed for them.
+    for (auto& pair : mInfos) {
+        if (pair.second.state == kStopped) {
+            continue;
+        }
+        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+                            ConditionState::kTrue;
+        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+        noteConditionChanged(pair.first, conditionMet, timestamp);
+    }
+}
+
+void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+    for (auto& pair : mInfos) {
+        noteConditionChanged(pair.first, condition, timestamp);
+    }
+}
+
+void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
+                                              const uint64_t timestamp) {
+    auto it = mInfos.find(key);
+    if (it == mInfos.end()) {
+        return;
+    }
+
+    switch (it->second.state) {
+        case kStarted:
+            // if condition becomes false, kStarted -> kPaused. Record the current duration.
+            if (!conditionMet) {
+                it->second.state = DurationState::kPaused;
+                it->second.lastDuration += (timestamp - it->second.lastStartTime);
+
+                VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
+            }
+            break;
+        case kStopped:
+            // nothing to do if it's stopped.
+            break;
+        case kPaused:
+            // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+            // change time.
+            if (conditionMet) {
+                it->second.state = DurationState::kStarted;
+                it->second.lastStartTime = timestamp;
+                VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
+            }
+            break;
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
new file mode 100644
index 0000000..c74d070
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -0,0 +1,55 @@
+/*
+ * 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 MAX_DURATION_TRACKER_H
+#define MAX_DURATION_TRACKER_H
+
+#include "DurationTracker.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Tracks a pool of atom durations, and output the max duration for each bucket.
+// To get max duration, we need to keep track of each individual durations, and compare them when
+// they stop or bucket expires.
+class MaxDurationTracker : public DurationTracker {
+public:
+    MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                       std::vector<DurationBucketInfo>& bucket);
+    void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
+                   const ConditionKey& conditionKey) override;
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+    void noteStopAll(const uint64_t eventTime) override;
+    bool flushIfNeeded(uint64_t timestampNs) override;
+    void onSlicedConditionMayChange(const uint64_t timestamp) override;
+    void onConditionChanged(bool condition, const uint64_t timestamp) override;
+
+private:
+    std::map<HashableDimensionKey, DurationInfo> mInfos;
+
+    void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
+                              const uint64_t timestamp);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // MAX_DURATION_TRACKER_H
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
new file mode 100644
index 0000000..e045fb4
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -0,0 +1,195 @@
+/*
+ * 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
+#include "Log.h"
+#include "OringDurationTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                                           uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                           std::vector<DurationBucketInfo>& bucket)
+    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket),
+      mStarted(),
+      mPaused() {
+    mLastStartTime = 0;
+}
+
+void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
+                                     const uint64_t eventTime, const ConditionKey& conditionKey) {
+    if (condition) {
+        if (mStarted.size() == 0) {
+            mLastStartTime = eventTime;
+            VLOG("record first start....");
+        }
+        mStarted.insert(key);
+    } else {
+        mPaused.insert(key);
+    }
+
+    if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+        mConditionKeyMap[key] = conditionKey;
+    }
+
+    VLOG("Oring: %s start, condition %d", key.c_str(), condition);
+}
+
+void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp) {
+    VLOG("Oring: %s stop", key.c_str());
+    auto it = mStarted.find(key);
+    if (it != mStarted.end()) {
+        mStarted.erase(it);
+        if (mStarted.empty()) {
+            mDuration += (timestamp - mLastStartTime);
+            VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
+                 (long long)mDuration);
+        }
+    }
+
+    mPaused.erase(key);
+    mConditionKeyMap.erase(key);
+}
+void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
+    if (!mStarted.empty()) {
+        mDuration += (timestamp - mLastStartTime);
+        VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
+             (long long)mDuration);
+    }
+
+    mStarted.clear();
+    mPaused.clear();
+    mConditionKeyMap.clear();
+}
+
+bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+        return false;
+    }
+    VLOG("OringDurationTracker Flushing.............");
+    // adjust the bucket start time
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    DurationBucketInfo info;
+    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(endTime);
+
+    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+
+    if (mStarted.size() > 0) {
+        mDuration += (endTime - mLastStartTime);
+    }
+    if (mDuration != 0) {
+        info.set_duration_nanos(mDuration);
+        // it will auto create new vector of CountbucketInfo if the key is not found.
+        mBucket.push_back(info);
+        VLOG("  duration: %lld", (long long)mDuration);
+    }
+
+    if (mStarted.size() > 0) {
+        for (int i = 1; i < numBucketsForward; i++) {
+            DurationBucketInfo info;
+            info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+            info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+            info.set_duration_nanos(mBucketSizeNs);
+            mBucket.push_back(info);
+            VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
+        }
+    }
+    mLastStartTime = mCurrentBucketStartTimeNs;
+    mDuration = 0;
+
+    // if all stopped, then tell owner it's safe to remove this tracker.
+    return mStarted.empty() && mPaused.empty();
+}
+
+void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+    vector<HashableDimensionKey> startedToPaused;
+    vector<HashableDimensionKey> pausedToStarted;
+    if (!mStarted.empty()) {
+        for (auto it = mStarted.begin(); it != mStarted.end();) {
+            auto key = *it;
+            if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+                VLOG("Key %s dont have condition key", key.c_str());
+                ++it;
+                continue;
+            }
+            if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
+                ConditionState::kTrue) {
+                it = mStarted.erase(it);
+                startedToPaused.push_back(key);
+                VLOG("Key %s started -> paused", key.c_str());
+            } else {
+                ++it;
+            }
+        }
+
+        if (mStarted.empty()) {
+            mDuration += (timestamp - mLastStartTime);
+            VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
+                 (long long)mDuration);
+        }
+    }
+
+    if (!mPaused.empty()) {
+        for (auto it = mPaused.begin(); it != mPaused.end();) {
+            auto key = *it;
+            if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+                VLOG("Key %s dont have condition key", key.c_str());
+                ++it;
+                continue;
+            }
+            if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
+                ConditionState::kTrue) {
+                it = mPaused.erase(it);
+                pausedToStarted.push_back(key);
+                VLOG("Key %s paused -> started", key.c_str());
+            } else {
+                ++it;
+            }
+        }
+
+        if (mStarted.empty() && pausedToStarted.size() > 0) {
+            mLastStartTime = timestamp;
+        }
+    }
+
+    mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
+    mPaused.insert(startedToPaused.begin(), startedToPaused.end());
+}
+
+void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+    if (condition) {
+        if (!mPaused.empty()) {
+            VLOG("Condition true, all started");
+            if (mStarted.empty()) {
+                mLastStartTime = timestamp;
+            }
+            mStarted.insert(mPaused.begin(), mPaused.end());
+        }
+    } else {
+        if (!mStarted.empty()) {
+            VLOG("Condition false, all paused");
+            mDuration += (timestamp - mLastStartTime);
+            mPaused.insert(mStarted.begin(), mStarted.end());
+        }
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
new file mode 100644
index 0000000..5425251
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -0,0 +1,57 @@
+/*
+ * 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 ORING_DURATION_TRACKER_H
+#define ORING_DURATION_TRACKER_H
+
+#include "DurationTracker.h"
+
+#include <set>
+namespace android {
+namespace os {
+namespace statsd {
+
+// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
+class OringDurationTracker : public DurationTracker {
+public:
+    OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+                         uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                         std::vector<DurationBucketInfo>& bucket);
+    void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
+                   const ConditionKey& conditionKey) override;
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+    void noteStopAll(const uint64_t eventTime) override;
+    void onSlicedConditionMayChange(const uint64_t timestamp) override;
+    void onConditionChanged(bool condition, const uint64_t timestamp) override;
+    bool flushIfNeeded(uint64_t timestampNs) override;
+
+private:
+    // We don't need to keep track of individual durations. The information that's needed is:
+    // 1) which keys are started. We record the first start time.
+    // 2) which keys are paused (started but condition was false)
+    // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty,
+    //    it means everything has stopped, we then record the end time.
+    std::set<HashableDimensionKey> mStarted;
+    std::set<HashableDimensionKey> mPaused;
+    int64_t mLastStartTime;
+    std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // ORING_DURATION_TRACKER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index e90f998..3d4036e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -16,11 +16,13 @@
 
 #include "../condition/CombinationConditionTracker.h"
 #include "../condition/SimpleConditionTracker.h"
+#include "../external/StatsPullerManager.h"
 #include "../matchers/CombinationLogMatchingTracker.h"
 #include "../matchers/SimpleLogMatchingTracker.h"
 #include "CountMetricProducer.h"
 #include "DurationMetricProducer.h"
 #include "EventMetricProducer.h"
+#include "ValueMetricProducer.h"
 #include "stats_util.h"
 
 using std::set;
@@ -33,6 +35,8 @@
 namespace statsd {
 
 bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+                                 const bool usedForDimension,
+                                 const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                                  const unordered_map<string, int>& logTrackerMap,
                                  unordered_map<int, std::vector<int>>& trackerToMetricMap,
                                  int& logTrackerIndex) {
@@ -41,6 +45,12 @@
         ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
         return false;
     }
+    if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
+        ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the "
+              "\"what\" can only about one atom type.",
+              what.c_str());
+        return false;
+    }
     logTrackerIndex = logTrackerIt->second;
     auto& metric_list = trackerToMetricMap[logTrackerIndex];
     metric_list.push_back(metricIndex);
@@ -68,8 +78,7 @@
         }
         allConditionTrackers[condition_it->second]->setSliced(true);
         allConditionTrackers[it->second]->setSliced(true);
-        allConditionTrackers[it->second]->addDimensions(
-                vector<KeyMatcher>(link.key_in_condition().begin(), link.key_in_condition().end()));
+        // TODO: We need to verify the link is valid.
     }
     conditionIndex = condition_it->second;
 
@@ -176,6 +185,7 @@
 
 bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
                  const unordered_map<string, int>& conditionTrackerMap,
+                 const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
                  vector<sp<MetricProducer>>& allMetricProducers,
                  unordered_map<int, std::vector<int>>& conditionToMetricMap,
@@ -184,6 +194,7 @@
     const int allMetricsCount =
             config.count_metric_size() + config.duration_metric_size() + config.event_metric_size();
     allMetricProducers.reserve(allMetricsCount);
+    StatsPullerManager& statsPullerManager = StatsPullerManager::GetInstance();
 
     // Build MetricProducers for each metric defined in config.
     // (1) build CountMetricProducer
@@ -196,8 +207,9 @@
 
         int metricIndex = allMetricProducers.size();
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+                                         allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+                                         trackerIndex)) {
             return false;
         }
 
@@ -215,26 +227,49 @@
     for (int i = 0; i < config.duration_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const DurationMetric& metric = config.duration_metric(i);
+
+        auto what_it = conditionTrackerMap.find(metric.what());
+        if (what_it == conditionTrackerMap.end()) {
+            ALOGE("DurationMetric's \"what\" is invalid");
+            return false;
+        }
+
+        const Condition& durationWhat = config.condition(what_it->second);
+
+        if (durationWhat.contents_case() != Condition::ContentsCase::kSimpleCondition) {
+            ALOGE("DurationMetric's \"what\" must be a simple condition");
+            return false;
+        }
+
+        const auto& simpleCondition = durationWhat.simple_condition();
+
         int trackerIndices[3] = {-1, -1, -1};
-        if (!metric.has_start() ||
-            !handleMetricWithLogTrackers(metric.start(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndices[0])) {
+        if (!simpleCondition.has_start() ||
+            !handleMetricWithLogTrackers(simpleCondition.start(), metricIndex,
+                                         metric.dimension_size() > 0, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
             ALOGE("Duration metrics must specify a valid the start event matcher");
             return false;
         }
 
-        if (metric.has_stop() &&
-            !handleMetricWithLogTrackers(metric.stop(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndices[1])) {
+        if (simpleCondition.has_stop() &&
+            !handleMetricWithLogTrackers(simpleCondition.stop(), metricIndex,
+                                         metric.dimension_size() > 0, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
             return false;
         }
 
-        if (metric.has_stop_all() &&
-            !handleMetricWithLogTrackers(metric.stop_all(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndices[2])) {
+        if (simpleCondition.has_stop_all() &&
+            !handleMetricWithLogTrackers(simpleCondition.stop_all(), metricIndex,
+                                         metric.dimension_size() > 0, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
             return false;
         }
 
+        vector<KeyMatcher> internalDimension;
+        internalDimension.insert(internalDimension.begin(), simpleCondition.dimension().begin(),
+                                 simpleCondition.dimension().end());
+
         int conditionIndex = -1;
 
         if (metric.has_predicate()) {
@@ -243,9 +278,9 @@
                                        conditionToMetricMap);
         }
 
-        sp<MetricProducer> durationMetric =
-                new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
-                                           trackerIndices[1], trackerIndices[2], wizard);
+        sp<MetricProducer> durationMetric = new DurationMetricProducer(
+                metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
+                wizard, internalDimension);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -258,8 +293,8 @@
             return false;
         }
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
-                                         trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allLogEntryMatchers,
+                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -275,6 +310,34 @@
         allMetricProducers.push_back(eventMetric);
     }
 
+    // value metrics
+    for (int i = 0; i < config.value_metric_size(); i++) {
+        const ValueMetric& metric = config.value_metric(i);
+        if (!metric.has_what()) {
+            ALOGW("cannot find what in ValueMetric %lld", metric.metric_id());
+            return false;
+        }
+
+        int pullCode = statsPullerManager.GetPullCode(metric.what());
+        if (pullCode == -1) {
+            ALOGW("cannot find %s in pulled metrics", metric.what().c_str());
+            return false;
+        }
+
+        sp<MetricProducer> valueProducer;
+        auto condition_it = conditionTrackerMap.find(metric.condition());
+        if (condition_it == conditionTrackerMap.end()) {
+            ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
+            return false;
+        }
+        int metricIndex = allMetricProducers.size();
+        valueProducer = new ValueMetricProducer(metric, condition_it->second, wizard);
+        // will create new vector if not exist before.
+        auto& metricList = conditionToMetricMap[condition_it->second];
+        metricList.push_back(metricIndex);
+
+        allMetricProducers.push_back(valueProducer);
+    }
     return true;
 }
 
@@ -299,8 +362,9 @@
         return false;
     }
 
-    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
-                     allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
+    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
+                     allConditionTrackers, allMetricProducers, conditionToMetricMap,
+                     trackerToMetricMap)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 6722eb3..e089d065 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "../condition/ConditionTracker.h"
+#include "../external/StatsPullerManager.h"
 #include "../matchers/LogMatchingTracker.h"
 
 namespace android {
@@ -75,6 +76,7 @@
         const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
         const std::unordered_map<std::string, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+        const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
         vector<sp<ConditionTracker>>& allConditionTrackers,
         std::vector<sp<MetricProducer>>& allMetricProducers,
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index 8b948de..13e776f 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -29,6 +29,9 @@
     // Uid map will notify this listener that the app with apk name and uid has been upgraded to
     // the specified version.
     virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int version) = 0;
+
+    // Notify interested listeners that the given apk and uid combination no longer exits.
+    virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index f4621ee..d83c3a4 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -52,25 +52,35 @@
 
 void UidMap::updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
                        const vector<String16>& packageName) {
+    updateMap(time(nullptr) * 1000000000, uid, versionCode, packageName);
+}
+
+void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
+                       const vector<int32_t>& versionCode, const vector<String16>& packageName) {
     lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
 
     mMap.clear();
-    for (unsigned long j = 0; j < uid.size(); j++) {
+    for (size_t j = 0; j < uid.size(); j++) {
         mMap.insert(make_pair(uid[j],
                               AppData(string(String8(packageName[j]).string()), versionCode[j])));
     }
 
-    if (mOutput.initial_size() == 0) {  // Provide the initial states in the mOutput proto
-        for (unsigned long j = 0; j < uid.size(); j++) {
-            auto t = mOutput.add_initial();
-            t->set_app(string(String8(packageName[j]).string()));
-            t->set_version(int(versionCode[j]));
-            t->set_uid(uid[j]);
-        }
+    auto snapshot = mOutput.add_snapshots();
+    snapshot->set_timestamp_nanos(timestamp);
+    for (size_t j = 0; j < uid.size(); j++) {
+        auto t = snapshot->add_package_info();
+        t->set_name(string(String8(packageName[j]).string()));
+        t->set_version(int(versionCode[j]));
+        t->set_uid(uid[j]);
     }
 }
 
 void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
+    updateApp(time(nullptr) * 1000000000, app_16, uid, versionCode);
+}
+
+void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
+                       const int32_t& versionCode) {
     lock_guard<mutex> lock(mMutex);
 
     string app = string(String8(app_16).string());
@@ -82,7 +92,7 @@
 
     auto log = mOutput.add_changes();
     log->set_deletion(false);
-    // log.timestamp = TODO: choose how timestamps are computed
+    log->set_timestamp_nanos(timestamp);
     log->set_app(app);
     log->set_uid(uid);
     log->set_version(versionCode);
@@ -102,13 +112,20 @@
 }
 
 void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
+    removeApp(time(nullptr) * 1000000000, app_16, uid);
+}
+void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
     lock_guard<mutex> lock(mMutex);
 
     string app = string(String8(app_16).string());
 
+    for (auto it : mSubscribers) {
+        it->notifyAppRemoved(app, uid);
+    }
+
     auto log = mOutput.add_changes();
     log->set_deletion(true);
-    // log.timestamp = TODO: choose how timestamps are computed
+    log->set_timestamp_nanos(timestamp);
     log->set_app(app);
     log->set_uid(uid);
 
@@ -133,21 +150,67 @@
     mSubscribers.erase(producer);
 }
 
-UidMapping UidMap::getAndClearOutput() {
-    lock_guard<mutex> lock(mMutex);  // Lock for updates
-
-    auto ret = UidMapping(mOutput);  // Copy that will be returned.
+void UidMap::clearOutput() {
     mOutput.Clear();
 
     // Re-initialize the initial state for the outputs. This results in extra data being uploaded
-    // but helps ensure we can't re-construct the UID->app name, versionCode mapping in server.
+    // but helps ensure we can re-construct the UID->app name, versionCode mapping in server.
+    auto snapshot = mOutput.add_snapshots();
     for (auto it : mMap) {
-        auto t = mOutput.add_initial();
-        t->set_app(it.second.packageName);
+        auto t = snapshot->add_package_info();
+        t->set_name(it.second.packageName);
         t->set_version(it.second.versionCode);
         t->set_uid(it.first);
     }
+}
 
+int64_t UidMap::getMinimumTimestampNs() {
+    int64_t m = 0;
+    for (auto it : mLastUpdatePerConfigKey) {
+        if (m == 0) {
+            m = it.second;
+        } else if (it.second < m) {
+            m = it.second;
+        }
+    }
+    return m;
+}
+
+UidMapping UidMap::getOutput(const ConfigKey& key) {
+    return getOutput(time(nullptr) * 1000000000, key);
+}
+
+UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) {
+    lock_guard<mutex> lock(mMutex);  // Lock for updates
+
+    auto ret = UidMapping(mOutput);  // Copy that will be returned.
+    int64_t prevMin = getMinimumTimestampNs();
+    mLastUpdatePerConfigKey[key] = timestamp;
+    int64_t newMin = getMinimumTimestampNs();
+
+    if (newMin > prevMin) {
+        int64_t cutoff_nanos = newMin;
+        auto snapshots = mOutput.mutable_snapshots();
+        auto it_snapshots = snapshots->cbegin();
+        while (it_snapshots != snapshots->cend()) {
+            if (it_snapshots->timestamp_nanos() < cutoff_nanos) {
+                // it_snapshots now points to the following element.
+                it_snapshots = snapshots->erase(it_snapshots);
+            } else {
+                ++it_snapshots;
+            }
+        }
+        auto deltas = mOutput.mutable_changes();
+        auto it_deltas = deltas->cbegin();
+        while (it_deltas != deltas->cend()) {
+            if (it_deltas->timestamp_nanos() < cutoff_nanos) {
+                // it_deltas now points to the following element.
+                it_deltas = deltas->erase(it_deltas);
+            } else {
+                ++it_deltas;
+            }
+        }
+    }
     return ret;
 }
 
@@ -160,6 +223,14 @@
     }
 }
 
+void UidMap::OnConfigUpdated(const ConfigKey& key) {
+    mLastUpdatePerConfigKey[key] = -1;
+}
+
+void UidMap::OnConfigRemoved(const ConfigKey& key) {
+    mLastUpdatePerConfigKey.erase(key);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index d550372..bf120e0 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -17,11 +17,14 @@
 #ifndef STATSD_UIDMAP_H
 #define STATSD_UIDMAP_H
 
+#include "config/ConfigKey.h"
+#include "config/ConfigListener.h"
 #include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
 #include "packages/PackageInfoListener.h"
 
 #include <binder/IResultReceiver.h>
 #include <binder/IShellCallback.h>
+#include <gtest/gtest_prod.h>
 #include <log/logprint.h>
 #include <stdio.h>
 #include <utils/RefBase.h>
@@ -51,17 +54,19 @@
      * All three inputs must be the same size, and the jth element in each array refers to the same
      * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
      */
+    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
     void updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
                    const vector<String16>& packageName);
 
+    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
+    void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
+    void removeApp(const String16& packageName, const int32_t& uid);
+
     // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
     bool hasApp(int uid, const string& packageName) const;
 
     int getAppVersion(int uid, const string& packageName) const;
 
-    void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
-    void removeApp(const String16& packageName, const int32_t& uid);
-
     // Helper for debugging contents of this uid map. Can be triggered with:
     // adb shell cmd stats print-uid-map
     void printUidMap(FILE* out);
@@ -73,13 +78,36 @@
     // Remove the listener from the set of metric producers that subscribe to updates.
     void removeListener(sp<PackageInfoListener> producer);
 
-    // Grabs the current output contents and then clears it.
-    UidMapping getAndClearOutput();
+    // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
+    void OnConfigUpdated(const ConfigKey& key);
+
+    // Informs uid map that a config is removed. Used for keeping mConfigKeys up to date.
+    void OnConfigRemoved(const ConfigKey& key);
+
+    // Gets the output. If every config key has received the output, then the output is cleared.
+    UidMapping getOutput(const ConfigKey& key);
+
+    // Forces the output to be cleared. We still generate a snapshot based on the current state.
+    // This results in extra data uploaded but helps us reconstruct the uid mapping on the server
+    // in case we lose a previous upload.
+    void clearOutput();
 
 private:
+    void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
+                   const vector<int32_t>& versionCode, const vector<String16>& packageName);
+
+    // TODO: Add safeguards to call clearOutput if there's too much data already stored.
+    void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid,
+                   const int32_t& versionCode);
+    void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid);
+
+    UidMapping getOutput(const int64_t& timestamp, const ConfigKey& key);
+
     // TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
     mutable mutex mMutex;
 
+    // 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;
 
     // We prepare the output proto as apps are updated, so that we can grab the current output.
@@ -87,6 +115,17 @@
 
     // Metric producers that should be notified if there's an upgrade in any app.
     set<sp<PackageInfoListener>> mSubscribers;
+
+    // Mapping of config keys we're aware of to the epoch time they last received an update. This
+    // lets us know it's safe to delete events older than the oldest update. The value is nanosec.
+    // Value of -1 denotes this config key has never received an upload.
+    std::unordered_map<ConfigKey, int64_t> mLastUpdatePerConfigKey;
+
+    // Returns the minimum value from mConfigKeys.
+    int64_t getMinimumTimestampNs();
+
+    // Allows unit-test to access private methods.
+    FRIEND_TEST(UidMapTest, TestClearingOutput);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index 74ee332..51244c6 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -35,7 +35,8 @@
  * in the format defined here and in stats_log.proto.
  */
 message StatsEvent {
-    oneof event {
+    // Pushed events start at 2.
+    oneof pushed {
         // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
         BleScanStateChanged ble_scan_state_changed = 2;
         BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3;
@@ -69,9 +70,19 @@
         WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
         WifiScanStateChanged wifi_scan_state_changed = 39;
         PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
-
+        SettingChanged setting_changed = 41;
+        ActivityForegroundStateChanged activity_foreground_state_changed = 42;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
+
+    // Pulled events will start at field 1000.
+    oneof pulled {
+        WifiBytesTransferred wifi_bytes_transferred = 1000;
+        WifiBytesTransferredByFgBg wifi_bytes_transferred_by_fg_bg = 1001;
+        MobileBytesTransferred mobile_bytes_transferred = 1002;
+        MobileBytesTransferredByFgBg mobile_bytes_transferred_by_fg_bg = 1003;
+        KernelWakelocksReported kernel_wakelocks_reported = 1004;
+    }
 }
 
 /**
@@ -479,7 +490,7 @@
  * Logs battery level (percent full, from 0 to 100).
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message BatteryLevelChanged {
     // Battery level. Should be in [0, 100].
@@ -490,7 +501,7 @@
  * Logs change in charging status of the device.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message ChargingStateChanged {
     // TODO: Link directly to BatteryManager.java's constants (via a proto).
@@ -508,7 +519,7 @@
  * Logs whether the device is plugged in, and what power source it is using.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message PluggedStateChanged {
     // TODO: Link directly to BatteryManager.java's constants (via a proto).
@@ -529,7 +540,7 @@
  * Logs the temperature of the device, in tenths of a degree Celsius.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message DeviceTemperatureReported {
     // Temperature in tenths of a degree C.
@@ -542,7 +553,7 @@
  * Logs when the device turns off or on.
  *
  * Logged from:
-  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  */
 message DeviceOnStatusChanged {
     enum State {
@@ -556,7 +567,7 @@
  * Logs when an app's wakeup alarm fires.
  *
  * Logged from:
-  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  */
 message WakeupAlarmOccurred {
     // TODO: Add attribution instead of uid?
@@ -581,7 +592,7 @@
  * Logs wifi locks held by an app.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiLockStateChanged {
     // TODO: Add attribution instead of uid.
@@ -598,7 +609,7 @@
  * Logs wifi signal strength changes.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiSignalStrengthChanged {
     // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
@@ -616,7 +627,7 @@
  * Logs wifi scans performed by an app.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiScanStateChanged {
     // TODO: Add attribution instead of uid.
@@ -633,7 +644,7 @@
  * Logs phone signal strength changes.
  *
  * Logged from:
-  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message PhoneSignalStrengthChanged {
     // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
@@ -645,4 +656,150 @@
         SIGNAL_STRENGTH_GREAT = 4;
     }
     optional SignalStrength signal_strength = 1;
-}
\ No newline at end of file
+}
+
+/**
+ * Logs that a setting was updated.
+ * Logged from:
+ *   frameworks/base/core/java/android/provider/Settings.java
+ * The tag and is_default allow resetting of settings to default values based on the specified
+ * tag. See Settings#putString(ContentResolver, String, String, String, boolean) for more details.
+ */
+message SettingChanged {
+    // The name of the setting.
+    optional string setting = 1;
+
+    // The change being imposed on this setting. May represent a number, eg "3".
+    optional string value = 2;
+
+    // The new value of this setting. For most settings, this is same as value. For some settings,
+    // value is +X or -X where X represents an element in a set. For example, if the previous value
+    // is A,B,C and value is -B, then new_value is A,C and prev_value is A,B,C.
+    // The +/- feature is currently only used for location_providers_allowed.
+    optional string new_value = 3;
+
+    // The previous value of this setting.
+    optional string prev_value = 4;
+
+    // The tag used with the is_default for resetting sets of settings. This is generally null.
+    optional string tag = 5;
+
+    // 1 indicates that this setting with tag should be resettable.
+    optional int32 is_default = 6;
+
+    // The user ID associated. Defined in android/os/UserHandle.java
+    optional int32 user = 7;
+}
+
+
+/*
+ * Logs activity going to foreground or background
+ *
+ * Logged from:
+  *   frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
+ */
+message ActivityForegroundStateChanged {
+    enum Activity {
+        MOVE_TO_BACKGROUND = 0;
+        MOVE_TO_FOREGROUND = 1;
+    }
+    optional int32 uid = 1;
+    optional string pkg_name = 2;
+    optional string class_name = 3;
+    optional Activity activity = 4;
+}
+
+/**
+ * Pulls bytes transferred via wifi (Sum of foreground and background usage).
+ *
+ * Pulled from:
+ *   StatsCompanionService (using BatteryStats to get which interfaces are wifi)
+ */
+message WifiBytesTransferred {
+    optional int32 uid = 1;
+
+    optional int64 rx_bytes = 2;
+
+    optional int64 rx_packets = 3;
+
+    optional int64 tx_bytes = 4;
+
+    optional int64 tx_packets = 5;
+}
+
+/**
+ * Pulls bytes transferred via wifi (separated by foreground and background usage).
+ *
+ * Pulled from:
+ *   StatsCompanionService (using BatteryStats to get which interfaces are wifi)
+ */
+message WifiBytesTransferredByFgBg {
+    optional int32 uid = 1;
+
+    // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats.
+    optional int32 is_foreground = 2;
+
+    optional int64 rx_bytes = 3;
+
+    optional int64 rx_packets = 4;
+
+    optional int64 tx_bytes = 5;
+
+    optional int64 tx_packets = 6;
+}
+
+/**
+ * Pulls bytes transferred via mobile networks (Sum of foreground and background usage).
+ *
+ * Pulled from:
+ *   StatsCompanionService (using BatteryStats to get which interfaces are mobile data)
+ */
+message MobileBytesTransferred {
+    optional int32 uid = 1;
+
+    optional int64 rx_bytes = 2;
+
+    optional int64 rx_packets = 3;
+
+    optional int64 tx_bytes = 4;
+
+    optional int64 tx_packets = 5;
+}
+
+/**
+ * Pulls bytes transferred via mobile networks (separated by foreground and background usage).
+ *
+ * Pulled from:
+ *   StatsCompanionService (using BatteryStats to get which interfaces are mobile data)
+ */
+message MobileBytesTransferredByFgBg {
+    optional int32 uid = 1;
+
+    // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats.
+    optional int32 is_foreground = 2;
+
+    optional int64 rx_bytes = 3;
+
+    optional int64 rx_packets = 4;
+
+    optional int64 tx_bytes = 5;
+
+    optional int64 tx_packets = 6;
+}
+
+/**
+ * Pulls the kernel wakelock durations. This atom is adapted from
+ * android/internal/os/KernelWakelockStats.java
+ *
+ * Pulled from:
+ *   StatsCompanionService using KernelWakelockReader.
+ */
+message KernelWakelocksReported {
+    optional string name = 1;
+
+    optional int32 count = 2;
+
+    optional int32 version = 3;
+
+    optional int64 time = 4;
+}
diff --git a/cmds/statsd/src/stats_events_copy.proto b/cmds/statsd/src/stats_events_copy.proto
index 5e8ef24..9470372 100644
--- a/cmds/statsd/src/stats_events_copy.proto
+++ b/cmds/statsd/src/stats_events_copy.proto
@@ -40,9 +40,28 @@
  */
 message StatsEvent {
     oneof event {
-        ScreenStateChanged screen_state_changed = 1;
-        ProcessStateChanged process_state_changed = 2;
-        WakeLockChanged wakelock_changed = 3;
+        // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
+        BleScanStateChanged ble_scan_state_changed = 2;
+        BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3;
+        BleScanResultReceived ble_scan_result_received = 4;
+        SensorStateChanged sensor_state_changed = 5;
+        GpsScanStateChanged gps_scan_state_changed = 6; // TODO: untested
+        SyncStateChanged sync_state_changed = 7;
+        ScheduledJobStateChanged scheduled_job_state_changed = 8;
+        ScreenBrightnessChanged screen_brightness_changed = 9;
+        // 10-20 are temporarily reserved for wakelocks etc.
+        UidWakelockStateChanged uid_wakelock_state_changed = 11;
+        LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 12;
+        BatterySaverModeStateChanged battery_saver_mode_state_changed = 21;
+        DeviceIdleModeStateChanged device_idle_mode_state_changed = 22;
+        AudioStateChanged audio_state_changed = 23;
+        MediaCodecActivityChanged media_codec_activity_changed = 24;
+        CameraStateChanged camera_state_changed = 25;
+        FlashlightStateChanged flashlight_state_changed = 26;
+        UidProcessStateChanged uid_process_state_changed = 27;
+        ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
+        ScreenStateChanged screen_state_changed = 29;
+        // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 }
 
@@ -76,7 +95,7 @@
  *     and those UIDs will be translated in xxx to those strings.
  *
  * CONVENTIONS:
- *   - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange
+ *   - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange.
  *   - If there is a UID, it goes first. Think in an object-oriented fashion.
  * *****************************************************************************
  */
@@ -102,33 +121,347 @@
 }
 
 /**
- * Logs that the state of a process state, as per the activity manager has changed.
+ * Logs that the state of a process state, as per the activity manager, has changed.
  *
  * Logged from:
  *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
  */
-message ProcessStateChanged {
-    // TODO: Use the real (mapped) process states.
+message UidProcessStateChanged {
     optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
 
     // The state.
+    // TODO: Use the real (mapped) process states.
     optional int32 state = 2;
 }
 
 /**
- * Logs that the state of a wakelock has changed.
+ * Logs that a process started, finished, crashed, or ANRed.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ProcessLifeCycleStateChanged {
+    // TODO: Use the real (mapped) process states.
+    optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+
+    // TODO: What is this?
+    optional string name = 2;
+
+    // The state.
+    // TODO: Use an enum.
+    optional int32 event = 3;
+}
+
+
+
+/**
+ * Logs when the ble scan state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BleScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when an unoptimized ble scan state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleUnoptimizedScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs reporting of a ble scan finding results.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleScanResultReceived {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Number of ble scan results returned.
+    optional int32 num_of_results = 2;
+}
+
+/**
+ * Logs when a sensor state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SensorStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // TODO: Is there a way to get the actual name of the sensor?
+    // The id (int) of the sensor.
+    optional int32 sensor_id = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+}
+
+
+/**
+ * Logs when GPS state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message GpsScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+
+/**
+ * Logs when a sync manager sync state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SyncStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Name of the sync (as named in the app)
+    optional string name = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+}
+
+/**
+ * Logs when a job scheduler job state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ScheduledJobStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Name of the job (as named in the app)
+    optional string name = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+
+    // TODO: Consider adding the stopReason (int)
+}
+
+/**
+ * Logs when the audio state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message AudioStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when the video codec state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message MediaCodecActivityChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when the flashlight state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message FlashlightStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when the camera state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message CameraStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs that the state of a wakelock (per app and per wakelock name) has changed.
  *
  * Logged from:
  *   TODO
  */
-message WakeLockChanged {
+message WakelockChanged {
     // TODO: Add attribution instead of uid.
     optional int32 uid = 1;
 
+    // Type of wakelock.
+    enum Type {
+        PARTIAL = 0;
+        FULL = 1;
+        WINDOW = 2;
+    }
+    optional int32 type = 2;
+
+    // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
+    optional string tag = 3;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 4;
+}
+
+/**
+ * Logs when an app is holding a wakelock, regardless of the wakelock's name.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message UidWakelockStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Type of wakelock.
+    enum Type {
+        PARTIAL = 0;
+        FULL = 1;
+        WINDOW = 2;
+    }
+    optional int32 type = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+}
+
+/**
+ * Logs when a partial wakelock is considered 'long' (over 1 min).
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message LongPartialWakelockStateChanged {
+    // TODO: Add attribution instead of uid?
+    optional int32 uid = 1;
+
     // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
     optional string tag = 2;
 
-    // TODO: Use a constant instead of boolean?
-    optional bool state = 3;
+    // TODO: I have no idea what this is.
+    optional string history_tag = 3;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 4;
 }
 
+/**
+ * Logs Battery Saver state change.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatterySaverModeStateChanged {
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 1;
+}
+
+/**
+ * Logs Doze mode state change.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message DeviceIdleModeStateChanged {
+    // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_.
+    optional int32 state = 1;
+}
+
+/**
+ * Logs screen brightness level.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ScreenBrightnessChanged {
+    // Screen brightness level. Should be in [-1, 255] according to PowerManager.java.
+    optional int32 level = 1;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index ec91509..1e37ff8 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -98,15 +98,19 @@
 }
 
 message UidMapping {
-  message AppInfo {
-    optional string app = 1;
+  message PackageInfoSnapshot {
+    message PackageInfo {
+      optional string name = 1;
 
-    optional int32 version = 2;
+      optional int32 version = 2;
 
-    optional int32 uid = 3;
+      optional int32 uid = 3;
+    }
+    optional int64 timestamp_nanos = 1;
+
+    repeated PackageInfo package_info = 2;
   }
-
-  repeated AppInfo initial = 1;
+  repeated PackageInfoSnapshot snapshots = 1;
 
   message Change {
     optional bool deletion = 1;
@@ -139,9 +143,11 @@
   message ValueMetricDataWrapper {
     repeated ValueMetricData data = 1;
   }
+
   message GaugeMetricDataWrapper {
     repeated GaugeMetricData data = 1;
   }
+
   oneof data {
     EventMetricDataWrapper event_metrics = 4;
     CountMetricDataWrapper count_metrics = 5;
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 39c1d59..a428752 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -40,6 +40,8 @@
 
 typedef std::string HashableDimensionKey;
 
+typedef std::map<std::string, HashableDimensionKey> ConditionKey;
+
 EventMetricData parse(log_msg msg);
 
 int getTagId(log_msg msg);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index a4d2421..87884b33 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -82,6 +82,8 @@
   optional bool count_nesting = 3 [default = true];
 
   optional string stop_all = 4;
+
+  repeated KeyMatcher dimension = 5;
 }
 
 message Condition {
@@ -148,29 +150,24 @@
 message DurationMetric {
   optional int64 metric_id = 1;
 
-  optional string start = 2;
-
-  optional string stop = 3;
-
-  optional string stop_all = 4;
+  optional string what = 2;
 
   enum AggregationType {
     DURATION_SUM = 1;
 
     DURATION_MAX_SPARSE = 2;
-    DURATION_MIN_SPARSE = 3;
   }
-  optional AggregationType type = 5;
+  optional AggregationType type = 3;
 
-  optional string predicate = 6;
+  optional string predicate = 4;
 
-  repeated KeyMatcher dimension = 7;
+  repeated KeyMatcher dimension = 5;
 
-  optional Bucket bucket = 8;
+  optional Bucket bucket = 6;
 
-  repeated Alert alerts = 9;
+  repeated Alert alerts = 7;
 
-  repeated EventConditionLink links = 10;
+  repeated EventConditionLink links = 8;
 
 }
 
@@ -210,14 +207,7 @@
   repeated EventConditionLink links = 8;
 
   enum Operation {
-    SUM_DIFF = 1;
-    MIN_DIFF = 2;
-    MAX_DIFF = 3;
-    SUM = 4;
-    MIN = 5;
-    MAX = 6;
-    FIRST = 7;
-    LAST = 8;
+    SUM = 1;
   }
   optional Operation operation = 9 [default = SUM];
 }
diff --git a/cmds/statsd/tests/MaxDurationTracker_test.cpp b/cmds/statsd/tests/MaxDurationTracker_test.cpp
new file mode 100644
index 0000000..ae8bf42
--- /dev/null
+++ b/cmds/statsd/tests/MaxDurationTracker_test.cpp
@@ -0,0 +1,115 @@
+// 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "src/condition/ConditionWizard.h"
+#include "src/metrics/duration_helper/MaxDurationTracker.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+class MockConditionWizard : public ConditionWizard {
+public:
+    MOCK_METHOD2(
+            query,
+            ConditionState(const int conditionIndex,
+                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucketInfo> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("", true, bucketStartTimeNs, key1);
+    tracker.noteStop("", bucketStartTimeNs + 10);
+
+    tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStop("", bucketStartTimeNs + 40);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(20, buckets[0].duration_nanos());
+}
+
+TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucketInfo> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+    tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
+
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].duration_nanos());
+    EXPECT_EQ((long long)bucketSizeNs, buckets[1].duration_nanos());
+}
+
+TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    EXPECT_CALL(*wizard, query(_, key1))  // #4
+            .WillOnce(Return(ConditionState::kFalse));
+
+    vector<DurationBucketInfo> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t durationTimeNs = 2 * 1000;
+
+    MaxDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(5, buckets[0].duration_nanos());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index b000e13..e8e4d8b 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -18,6 +18,7 @@
 #include "src/matchers/LogMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
+#include "src/metrics/ValueMetricProducer.h"
 #include "src/metrics/metrics_manager_util.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -124,6 +125,39 @@
     return config;
 }
 
+StatsdConfig buildDimensionMetricsWithMultiTags() {
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("BATTERY_VERY_LOW");
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("BATTERY_VERY_VERY_LOW");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(3);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("BATTERY_LOW");
+
+    LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher("BATTERY_VERY_LOW");
+    combination->add_matcher("BATTERY_VERY_VERY_LOW");
+
+    // Count process state changes, slice by uid, while SCREEN_IS_OFF
+    CountMetric* metric = config.add_count_metric();
+    metric->set_metric_id(3);
+    metric->set_what("BATTERY_LOW");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(1);
+
+    return config;
+}
+
 StatsdConfig buildCircleConditions() {
     StatsdConfig config;
     config.set_config_id(12345L);
@@ -180,6 +214,21 @@
                                  trackerToConditionMap));
 }
 
+TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+    StatsdConfig config = buildDimensionMetricsWithMultiTags();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                                  trackerToConditionMap));
+}
+
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
     StatsdConfig config = buildCircleMatchers();
     set<int> allTagIds;
diff --git a/cmds/statsd/tests/OringDurationTracker_test.cpp b/cmds/statsd/tests/OringDurationTracker_test.cpp
new file mode 100644
index 0000000..0b79819
--- /dev/null
+++ b/cmds/statsd/tests/OringDurationTracker_test.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "src/condition/ConditionWizard.h"
+#include "src/metrics/duration_helper/OringDurationTracker.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+class MockConditionWizard : public ConditionWizard {
+public:
+    MOCK_METHOD2(
+            query,
+            ConditionState(const int conditionIndex,
+                           const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+TEST(OringDurationTrackerTest, TestDurationOverlap) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucketInfo> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t durationTimeNs = 2 * 1000;
+
+    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(durationTimeNs, buckets[0].duration_nanos());
+}
+
+TEST(OringDurationTrackerTest, TestDurationConditionChange) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    EXPECT_CALL(*wizard, query(_, key1))  // #4
+            .WillOnce(Return(ConditionState::kFalse));
+
+    vector<DurationBucketInfo> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    int64_t durationTimeNs = 2 * 1000;
+
+    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(5, buckets[0].duration_nanos());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index f9a90e4..671f6d4 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -13,13 +13,17 @@
 // limitations under the License.
 
 #include "packages/UidMap.h"
+#include "config/ConfigKey.h"
 
 #include <gtest/gtest.h>
 
 #include <stdio.h>
 
 using namespace android;
-using namespace android::os::statsd;
+
+namespace android {
+namespace os {
+namespace statsd {
 
 #ifdef __ANDROID__
 const string kApp1 = "app1.sharing.1";
@@ -64,6 +68,57 @@
     EXPECT_FALSE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
 }
+
+TEST(UidMapTest, TestClearingOutput) {
+    UidMap m;
+
+    ConfigKey config1(1, "config1");
+    ConfigKey config2(1, "config2");
+
+    m.OnConfigUpdated(config1);
+
+    vector<int32_t> uids;
+    vector<int32_t> versions;
+    vector<String16> apps;
+    uids.push_back(1000);
+    uids.push_back(1000);
+    apps.push_back(String16(kApp1.c_str()));
+    apps.push_back(String16(kApp2.c_str()));
+    versions.push_back(4);
+    versions.push_back(5);
+    m.updateMap(1, uids, versions, apps);
+
+    UidMapping results = m.getOutput(2, config1);
+    EXPECT_EQ(1, results.snapshots_size());
+
+    // It should be cleared now
+    results = m.getOutput(3, config1);
+    EXPECT_EQ(0, results.snapshots_size());
+
+    // Now add another configuration.
+    m.OnConfigUpdated(config2);
+    m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
+    results = m.getOutput(6, config1);
+    EXPECT_EQ(0, results.snapshots_size());
+    EXPECT_EQ(1, results.changes_size());
+
+    // Now we still haven't been able to delete anything
+    m.updateApp(7, String16(kApp2.c_str()), 1001, 41);
+    results = m.getOutput(8, config1);
+    EXPECT_EQ(0, results.snapshots_size());
+    EXPECT_EQ(2, results.changes_size());
+
+    results = m.getOutput(9, config2);
+    EXPECT_EQ(0, results.snapshots_size());
+    EXPECT_EQ(2, results.changes_size());
+    // At this point both should be cleared.
+    EXPECT_EQ(0, m.mOutput.snapshots_size());
+    EXPECT_EQ(0, m.mOutput.changes_size());
+}
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 076c2bc..a8863bf 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import static android.os.Build.VERSION_CODES.O_MR1;
-
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.CallSuper;
@@ -136,6 +134,7 @@
 import java.util.HashMap;
 import java.util.List;
 
+
 /**
  * An activity is a single, focused thing that the user can do.  Almost all
  * activities interact with the user, so the Activity class takes care of
@@ -991,17 +990,6 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
 
-        if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
-            final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
-            final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
-            ta.recycle();
-
-            if (isTranslucentOrFloating) {
-                throw new IllegalStateException(
-                        "Only fullscreen opaque activities can request orientation");
-            }
-        }
-
         if (mLastNonConfigurationInstances != null) {
             mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
         }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2305957..064e978 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -687,6 +687,7 @@
      * in portrait mode or at the left half of the screen if in landscape mode.
      * @hide
      */
+    @TestApi
     public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
 
     /**
@@ -696,6 +697,7 @@
      * in portrait mode or at the right half of the screen if in landscape mode.
      * @hide
      */
+    @TestApi
     public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
 
     /**
@@ -1926,6 +1928,33 @@
     }
 
     /**
+     * Moves the input task to the primary-split-screen stack.
+     * @param taskId Id of task to move.
+     * @param createMode The mode the primary split screen stack should be created in if it doesn't
+     *                  exist already. See
+     *                   {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+     *                   and
+     *                   {@link android.app.ActivityManager
+     *                        #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+     * @param toTop If the task and stack should be moved to the top.
+     * @param animate Whether we should play an animation for the moving the task
+     * @param initialBounds If the primary stack gets created, it will use these bounds for the
+     *                      docked stack. Pass {@code null} to use default bounds.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+            boolean animate, Rect initialBounds) throws SecurityException {
+        try {
+            getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
+                    initialBounds);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resizes the input stack id to the given bounds.
      * @param stackId Id of the stack to resize.
      * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 4b31b80..388459f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -232,7 +232,7 @@
             boolean requireFull, in String name, in String callerPackage);
     void addPackageDependency(in String packageName);
     void killApplication(in String pkg, int appId, int userId, in String reason);
-    void closeSystemDialogs(in String reason);
+    oneway void closeSystemDialogs(in String reason);
     Debug.MemoryInfo[] getProcessMemoryInfo(in int[] pids);
     void killApplicationProcess(in String processName, int uid);
     int startActivityIntentSender(in IApplicationThread caller,
@@ -450,9 +450,8 @@
     int checkPermissionWithToken(in String permission, int pid, int uid,
             in IBinder callerToken);
     void registerTaskStackListener(in ITaskStackListener listener);
+    void unregisterTaskStackListener(in ITaskStackListener listener);
 
-
-    // Start of M transactions
     void notifyCleartextNetwork(int uid, in byte[] firstPacket);
     int createStackOnDisplay(int displayId);
     void setTaskResizeable(int taskId, int resizeableMode);
@@ -618,7 +617,6 @@
      * @return Returns true if the configuration was updated.
      */
     boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
-    void unregisterTaskStackListener(ITaskStackListener listener);
     void moveStackToDisplay(int stackId, int displayId);
     boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
                                 in IBinder activityToken, int flags);
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 33825b4..da718dc 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -19,15 +19,19 @@
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Intent;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
+import android.os.UserHandle;
 import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
@@ -143,9 +147,13 @@
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         if (method.equals(METHOD_SLICE)) {
-            getContext().enforceCallingPermission(permission.BIND_SLICE,
-                    "Slice binding requires the permission BIND_SLICE");
             Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
+                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                        "Slice binding requires the permission BIND_SLICE");
+            }
 
             Slice s = handleBindSlice(uri);
             Bundle b = new Bundle();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index c827432..3a3e16e 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -261,7 +261,10 @@
 
     /**
      * @hide
+     * Changes the app standby state to the provided bucket.
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
     public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
         try {
             mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e47de75..dd729a3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3444,11 +3444,12 @@
     /**
      * A broadcast action to trigger a factory reset.
      *
-     * <p> The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission.
+     * <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
+     * reason for the factory reset should be specified as {@link #EXTRA_REASON}.
      *
      * <p>Not for use by third-party applications.
      *
-     * @see #EXTRA_FORCE_MASTER_CLEAR
+     * @see #EXTRA_FORCE_FACTORY_RESET
      *
      * {@hide}
      */
@@ -4827,7 +4828,13 @@
     /** @hide */
     public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
 
-    /** {@hide} */
+    /**
+     * Intent extra: the reason that the operation associated with this intent is being performed.
+     *
+     * <p>Type: String
+     * @hide
+     */
+    @SystemApi
     public static final String EXTRA_REASON = "android.intent.extra.REASON";
 
     /**
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 41667c4..837c00a 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -455,7 +455,6 @@
      */
     public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
 
-
     /**
      * @hide Bit in {@link #flags}: If set, this component will only be seen
      * by the system user.  Only works with broadcast receivers.  Set from the
@@ -1001,20 +1000,12 @@
      * Returns true if the activity's orientation is fixed.
      * @hide
      */
-    public boolean isFixedOrientation() {
+    boolean isFixedOrientation() {
         return isFixedOrientationLandscape() || isFixedOrientationPortrait()
                 || screenOrientation == SCREEN_ORIENTATION_LOCKED;
     }
 
     /**
-     * Returns true if the specified orientation is considered fixed.
-     * @hide
-     */
-    static public boolean isFixedOrientation(int orientation) {
-        return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
-    }
-
-    /**
      * Returns true if the activity's orientation is fixed to landscape.
      * @hide
      */
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 386239c..3239212 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -49,6 +49,8 @@
 import android.util.Xml;
 import android.view.DisplayAdjustments;
 
+import com.android.internal.util.GrowingArrayUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -117,6 +119,13 @@
     private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
             new ConfigurationBoundResourceCache<>();
 
+    // A stack of all the resourceIds already referenced when parsing a resource. This is used to
+    // detect circular references in the xml.
+    // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
+    // calls to ResourcesImpl
+    private final ThreadLocal<LookupStack> mLookupStack =
+            ThreadLocal.withInitial(() -> new LookupStack());
+
     /** Size of the cyclical cache used to map XML files to blocks. */
     private static final int XML_BLOCK_CACHE_SIZE = 4;
 
@@ -784,19 +793,29 @@
         final Drawable dr;
 
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+        LookupStack stack = mLookupStack.get();
         try {
-            if (file.endsWith(".xml")) {
-                final XmlResourceParser rp = loadXmlResourceParser(
-                        file, id, value.assetCookie, "drawable");
-                dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
-                rp.close();
-            } else {
-                final InputStream is = mAssets.openNonAsset(
-                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
-                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
-                is.close();
+            // Perform a linear search to check if we have already referenced this resource before.
+            if (stack.contains(id)) {
+                throw new Exception("Recursive reference in drawable");
             }
-        } catch (Exception | StackOverflowError e) {
+            stack.push(id);
+            try {
+                if (file.endsWith(".xml")) {
+                    final XmlResourceParser rp = loadXmlResourceParser(
+                            file, id, value.assetCookie, "drawable");
+                    dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+                    rp.close();
+                } else {
+                    final InputStream is = mAssets.openNonAsset(
+                            value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+                    dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
+                    is.close();
+                }
+            } finally {
+                stack.pop();
+            }
+        } catch (Exception e) {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
             final NotFoundException rnf = new NotFoundException(
                     "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
@@ -1377,4 +1396,29 @@
             }
         }
     }
+
+    private static class LookupStack {
+
+        // Pick a reasonable default size for the array, it is grown as needed.
+        private int[] mIds = new int[4];
+        private int mSize = 0;
+
+        public void push(int id) {
+            mIds = GrowingArrayUtils.append(mIds, mSize, id);
+            mSize++;
+        }
+
+        public boolean contains(int id) {
+            for (int i = 0; i < mSize; i++) {
+                if (mIds[i] == id) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void pop() {
+            mSize--;
+        }
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index c28583e..361b81b 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -289,7 +289,9 @@
 
     private void setWalModeFromConfiguration() {
         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
-            if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+            boolean walEnabled =
+                    (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+            if (walEnabled || mConfiguration.useCompatibilityWal) {
                 setJournalMode("WAL");
                 setSyncMode(SQLiteGlobal.getWALSyncMode());
             } else {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 2dc5ca4..13e6f71 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -22,6 +22,8 @@
 import android.os.StrictMode;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -60,6 +62,9 @@
     /** Used to find out where a cursor was allocated in case it never got released. */
     private final Throwable mStackTrace;
 
+    /** Controls fetching of rows relative to requested position **/
+    private boolean mFillWindowForwardOnly;
+
     /**
      * Execute a query and provide access to its result set through a Cursor
      * interface. For a query such as: {@code SELECT name, birth, phone FROM
@@ -136,18 +141,19 @@
 
     private void fillWindow(int requiredPos) {
         clearOrCreateWindow(getDatabase().getPath());
-
         try {
+            Preconditions.checkArgumentNonnegative(requiredPos,
+                    "requiredPos cannot be negative, but was " + requiredPos);
+
             if (mCount == NO_COUNT) {
-                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
-                mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+                mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
                 mCursorWindowCapacity = mWindow.getNumRows();
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
                     Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
                 }
             } else {
-                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
-                        mCursorWindowCapacity);
+                int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
+                        .cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
                 mQuery.fillWindow(mWindow, startPos, requiredPos, false);
             }
         } catch (RuntimeException ex) {
@@ -252,6 +258,20 @@
     }
 
     /**
+     * Controls fetching of rows relative to requested position.
+     *
+     * <p>Calling this method defines how rows will be loaded, but it doesn't affect rows that
+     * are already in the window. This setting is preserved if a new window is
+     * {@link #setWindow(CursorWindow) set}
+     *
+     * @param fillWindowForwardOnly if true, rows will be fetched starting from requested position
+     * up to the window's capacity. Default value is false.
+     */
+    public void setFillWindowForwardOnly(boolean fillWindowForwardOnly) {
+        mFillWindowForwardOnly = fillWindowForwardOnly;
+    }
+
+    /**
      * Release the native resources, if they haven't been released yet.
      */
     @Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index df0e262..83b8dc7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -285,6 +285,7 @@
             }
         }
         mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
+        mConfigurationLocked.useCompatibilityWal = SQLiteGlobal.isCompatibilityWalSupported();
     }
 
     @Override
@@ -2070,15 +2071,21 @@
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
+            final boolean oldUseCompatibilityWal = mConfigurationLocked.useCompatibilityWal;
+            final int oldFlags = mConfigurationLocked.openFlags;
+            if (!oldUseCompatibilityWal && (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
                 return;
             }
 
             mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+            // If an app explicitly disables WAL, do not even use compatibility mode
+            mConfigurationLocked.useCompatibilityWal = false;
+
             try {
                 mConnectionPoolLocked.reconfigure(mConfigurationLocked);
             } catch (RuntimeException ex) {
-                mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+                mConfigurationLocked.openFlags = oldFlags;
+                mConfigurationLocked.useCompatibilityWal = oldUseCompatibilityWal;
                 throw ex;
             }
         }
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 34c9b33..905da724 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -111,6 +111,15 @@
     public long idleConnectionTimeoutMs = Long.MAX_VALUE;
 
     /**
+     * Enables compatibility WAL mode. Applications cannot explicitly choose compatibility WAL mode,
+     * therefore it is not exposed as a flag.
+     *
+     * <p>In this mode, only database journal mode will be changed, connection pool
+     * size will still be limited to a single connection.
+     */
+    public boolean useCompatibilityWal;
+
+    /**
      * Creates a database configuration with the required parameters for opening a
      * database and default values for all other parameters.
      *
@@ -170,6 +179,7 @@
         lookasideSlotSize = other.lookasideSlotSize;
         lookasideSlotCount = other.lookasideSlotCount;
         idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
+        useCompatibilityWal = other.useCompatibilityWal;
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 94d5555..bb2a517 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -81,6 +81,17 @@
     }
 
     /**
+     * Returns true if compatibility WAL mode is supported. In this mode, only
+     * database journal mode is changed. Connection pool will use at most one connection.
+     * @hide
+     */
+    public static boolean isCompatibilityWalSupported() {
+        return SystemProperties.getBoolean("debug.sqlite.compatibility_wal_supported",
+                Resources.getSystem().getBoolean(
+                        com.android.internal.R.bool.db_compatibility_wal_supported));
+    }
+
+    /**
      * Gets the journal size limit in bytes.
      */
     public static int getJournalSizeLimit() {
diff --git a/core/java/android/os/BatteryStatsInternal.java b/core/java/android/os/BatteryStatsInternal.java
new file mode 100644
index 0000000..b0436eb
--- /dev/null
+++ b/core/java/android/os/BatteryStatsInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Battery stats local system service interface. This is used to pass internal data out of
+ * BatteryStatsImpl.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class BatteryStatsInternal {
+    /**
+     * Returns the wifi interfaces.
+     */
+    public abstract String[] getWifiIfaces();
+
+    /**
+     * Returns the mobile data interfaces.
+     */
+    public abstract String[] getMobileIfaces();
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 017c213..2e62eb6 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -23,7 +23,6 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.TypedProperties;
 
-import dalvik.bytecode.OpcodeInfo;
 import dalvik.system.VMDebug;
 
 import org.apache.harmony.dalvik.ddmc.Chunk;
@@ -48,8 +47,6 @@
 import java.util.Map;
 
 
-
-
 /**
  * Provides various debugging methods for Android applications, including
  * tracing and allocation counts.
@@ -1959,13 +1956,7 @@
      */
     @Deprecated
     public static class InstructionCount {
-        private static final int NUM_INSTR =
-            OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
-
-        private int[] mCounts;
-
         public InstructionCount() {
-            mCounts = new int[NUM_INSTR];
         }
 
         /**
@@ -1975,13 +1966,7 @@
          * @return true if counting was started
          */
         public boolean resetAndStart() {
-            try {
-                VMDebug.startInstructionCounting();
-                VMDebug.resetInstructionCount();
-            } catch (UnsupportedOperationException uoe) {
-                return false;
-            }
-            return true;
+            return false;
         }
 
         /**
@@ -1989,13 +1974,7 @@
          * counting process.
          */
         public boolean collect() {
-            try {
-                VMDebug.stopInstructionCounting();
-                VMDebug.getInstructionCount(mCounts);
-            } catch (UnsupportedOperationException uoe) {
-                return false;
-            }
-            return true;
+            return false;
         }
 
         /**
@@ -2003,13 +1982,7 @@
          * all threads).
          */
         public int globalTotal() {
-            int count = 0;
-
-            for (int i = 0; i < NUM_INSTR; i++) {
-                count += mCounts[i];
-            }
-
-            return count;
+            return 0;
         }
 
         /**
@@ -2017,15 +1990,7 @@
          * executed globally.
          */
         public int globalMethodInvocations() {
-            int count = 0;
-
-            for (int i = 0; i < NUM_INSTR; i++) {
-                if (OpcodeInfo.isInvoke(i)) {
-                    count += mCounts[i];
-                }
-            }
-
-            return count;
+            return 0;
         }
     }
 
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5b0e5bbc..f977c1d 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -836,7 +836,6 @@
      *         physically removed.
      */
     public static boolean isExternalStorageRemovable() {
-        if (isStorageDisabled()) return false;
         final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageRemovable(externalDir);
     }
@@ -875,7 +874,6 @@
      *      boolean)
      */
     public static boolean isExternalStorageEmulated() {
-        if (isStorageDisabled()) return false;
         final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageEmulated(externalDir);
     }
@@ -951,9 +949,6 @@
         return cur;
     }
 
-    private static boolean isStorageDisabled() {
-        return SystemProperties.getBoolean("config.disable_storage", false);
-    }
 
     /**
      * If the given path exists on emulated external storage, return the
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
index 7dec4d7..3544ea1 100644
--- a/core/java/android/os/HidlSupport.java
+++ b/core/java/android/os/HidlSupport.java
@@ -156,4 +156,27 @@
         // Should not reach here.
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Test that two interfaces are equal. This is the Java equivalent to C++
+     * interfacesEqual function.
+     * This essentially calls .equals on the internal binder objects (via Binder()).
+     * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder}
+     *   object, and they are compared in {@link HwRemoteBinder#equals}.
+     * - If both interfaces are stubs, asBinder() returns the object itself. By default,
+     *   auto-generated IFoo.Stub does not override equals(), but an implementation can
+     *   optionally override it, and {@code interfacesEqual} will use it here.
+     */
+    public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+        if (lft == rgt) {
+            return true;
+        }
+        if (lft == null || rgt == null) {
+            return false;
+        }
+        if (!(rgt instanceof IHwInterface)) {
+            return false;
+        }
+        return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
+    }
 }
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 270e63f..dd9e774 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -16,10 +16,10 @@
 
 package android.os;
 
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
 import libcore.util.NativeAllocationRegistry;
 
+import java.util.NoSuchElementException;
+
 /** @hide */
 public abstract class HwBinder implements IHwBinder {
     private static final String TAG = "HwBinder";
@@ -46,9 +46,16 @@
     public native final void registerService(String serviceName)
         throws RemoteException;
 
-    public static native final IHwBinder getService(
+    public static final IHwBinder getService(
             String iface,
             String serviceName)
+        throws RemoteException, NoSuchElementException {
+        return getService(iface, serviceName, false /* retry */);
+    }
+    public static native final IHwBinder getService(
+            String iface,
+            String serviceName,
+            boolean retry)
         throws RemoteException, NoSuchElementException;
 
     public static native final void configureRpcThreadpool(
@@ -56,6 +63,13 @@
 
     public static native final void joinRpcThreadpool();
 
+    /**
+     * Call configureRpcThreadpool, then actually spawn
+     * (maxThreads - (callerWillJoin ? 0 : 1)) threads.
+     */
+    public static final native void startRpcThreadPool(
+            long maxThreads, boolean callerWillJoin);
+
     // Returns address of the "freeFunction".
     private static native final long native_init();
 
diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java
index 88226f0..5e9b9ae3 100644
--- a/core/java/android/os/HwBlob.java
+++ b/core/java/android/os/HwBlob.java
@@ -43,6 +43,18 @@
     public native final double getDouble(long offset);
     public native final String getString(long offset);
 
+    /**
+      The copyTo... methods copy the blob's data, starting from the given
+      byte offset, into the array. A total of "size" _elements_ are copied.
+     */
+    public native final void copyToBoolArray(long offset, boolean[] array, int size);
+    public native final void copyToInt8Array(long offset, byte[] array, int size);
+    public native final void copyToInt16Array(long offset, short[] array, int size);
+    public native final void copyToInt32Array(long offset, int[] array, int size);
+    public native final void copyToInt64Array(long offset, long[] array, int size);
+    public native final void copyToFloatArray(long offset, float[] array, int size);
+    public native final void copyToDoubleArray(long offset, double[] array, int size);
+
     public native final void putBool(long offset, boolean x);
     public native final void putInt8(long offset, byte x);
     public native final void putInt16(long offset, short x);
@@ -52,6 +64,14 @@
     public native final void putDouble(long offset, double x);
     public native final void putString(long offset, String x);
 
+    public native final void putBoolArray(long offset, boolean[] x);
+    public native final void putInt8Array(long offset, byte[] x);
+    public native final void putInt16Array(long offset, short[] x);
+    public native final void putInt32Array(long offset, int[] x);
+    public native final void putInt64Array(long offset, long[] x);
+    public native final void putFloatArray(long offset, float[] x);
+    public native final void putDoubleArray(long offset, double[] x);
+
     public native final void putBlob(long offset, HwBlob blob);
 
     public native final long handle();
diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 2f89ce6..a07e42c 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -63,4 +63,9 @@
     }
 
     private long mNativeContext;
+
+    @Override
+    public final native boolean equals(Object other);
+    @Override
+    public final native int hashCode();
 }
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index 20f6c8e..c0a95cc 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -46,10 +46,10 @@
       * Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
       * and alarm is inexact.
       */
-    oneway void setPollingAlarms(long timestampMs, long intervalMs);
+    oneway void setPullingAlarms(long timestampMs, long intervalMs);
 
     /** Cancel any repeating polling alarm. */
-    oneway void cancelPollingAlarms();
+    oneway void cancelPullingAlarms();
 
     /** Pull the specified data. Results will be sent to statsd when complete. */
     StatsLogEventWrapper[] pullData(int pullCode);
diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java
index 9b3a2d6..00333dad 100644
--- a/core/java/android/os/TokenWatcher.java
+++ b/core/java/android/os/TokenWatcher.java
@@ -16,17 +16,23 @@
 
 package android.os;
 
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-import java.util.Set;
 import android.util.Log;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.WeakHashMap;
+
 /**
- * Helper class that helps you use IBinder objects as reference counted
- * tokens.  IBinders make good tokens because we find out when they are
- * removed
+ * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
+ * to the collection by calling {@link #acquire}, and removed by calling {@link
+ * #release}. IBinders are also implicitly removed when they become weakly
+ * reachable. Each IBinder may be added at most once.
  *
+ * The {@link #acquired} method is invoked by posting to the specified handler
+ * whenever the size of the watched collection becomes nonzero.  The {@link
+ * #released} method is invoked on the specified handler whenever the size of
+ * the watched collection becomes zero.
  */
 public abstract class TokenWatcher
 {
@@ -59,15 +65,23 @@
      * Record that this token has been acquired.  When acquire is called, and
      * the current count is 0, the acquired method is called on the given
      * handler.
-     * 
-     * @param token An IBinder object.  If this token has already been acquired,
-     *              no action is taken.
+     *
+     * Note that the same {@code token} can only be acquired once. If this
+     * {@code token} has already been acquired, no action is taken. The first
+     * subsequent call to {@link #release} will release this {@code token}
+     * immediately.
+     *
+     * @param token An IBinder object.
      * @param tag   A string used by the {@link #dump} method for debugging,
      *              to see who has references.
      */
     public void acquire(IBinder token, String tag)
     {
         synchronized (mTokens) {
+            if (mTokens.containsKey(token)) {
+                return;
+            }
+
             // explicitly checked to avoid bogus sendNotification calls because
             // of the WeakHashMap and the GC
             int oldSize = mTokens.size();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 62f4bf5..2d6a7b0 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -72,6 +72,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MemoryIntArray;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
@@ -1886,7 +1887,11 @@
                     arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
                 IContentProvider cp = mProviderHolder.getProvider(cr);
+                String prevValue = getStringForUser(cr, name, userHandle);
                 cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+                String newValue = getStringForUser(cr, name, userHandle);
+                StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag,
+                        makeDefault ? 1 : 0, userHandle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
                 return false;
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 43a9789..a94806a 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -127,42 +127,48 @@
 
     public static final long FIELD_TYPE_UNKNOWN = 0;
 
+    /**
+     * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+     * so no extra mapping needs to be maintained in this case.
+     */
     public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
     public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
-    public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+//  public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+    public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+    public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
 
     private static final String[] FIELD_TYPE_NAMES = new String[] {
         "Double",
         "Float",
-        "Int32",
         "Int64",
-        "UInt32",
         "UInt64",
-        "SInt32",
-        "SInt64",
-        "Fixed32",
+        "Int32",
         "Fixed64",
-        "SFixed32",
-        "SFixed64",
+        "Fixed32",
         "Bool",
         "String",
+        "Group",  // This field is deprecated but reserved here for indexing.
+        "Message",
         "Bytes",
+        "UInt32",
         "Enum",
-        "Object",
+        "SFixed32",
+        "SFixed64",
+        "SInt32",
+        "SInt64",
     };
 
     //
@@ -867,21 +873,21 @@
         assertNotCompacted();
         final int id = (int)fieldId;
 
-        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+        switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
             // bytes
-            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
                 writeBytesImpl(id, val);
                 break;
-            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
-            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
                 writeRepeatedBytesImpl(id, val);
                 break;
             // Object
-            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
                 writeObjectImpl(id, val);
                 break;
-            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
-            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
                 writeRepeatedObjectImpl(id, val);
                 break;
             // nothing else allowed
@@ -899,7 +905,7 @@
         assertNotCompacted();
         final int id = (int)fieldId;
 
-        if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+        if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
             final long count = fieldId & FIELD_COUNT_MASK;
             if (count == FIELD_COUNT_SINGLE) {
                 return startObjectImpl(id, false);
@@ -2091,7 +2097,7 @@
     @Deprecated
     public long startObject(long fieldId) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
 
         return startObjectImpl(id, false);
     }
@@ -2119,7 +2125,7 @@
     @Deprecated
     public long startRepeatedObject(long fieldId) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
 
         return startObjectImpl(id, true);
     }
@@ -2217,7 +2223,7 @@
     @Deprecated
     public void writeObject(long fieldId, byte[] value) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
 
         writeObjectImpl(id, value);
     }
@@ -2237,7 +2243,7 @@
     @Deprecated
     public void writeRepeatedObject(long fieldId, byte[] value) {
         assertNotCompacted();
-        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
 
         writeRepeatedObjectImpl(id, value);
     }
@@ -2296,7 +2302,7 @@
             final String typeString = getFieldTypeString(fieldType);
             if (typeString != null && countString != null) {
                 final StringBuilder sb = new StringBuilder();
-                if (expectedType == FIELD_TYPE_OBJECT) {
+                if (expectedType == FIELD_TYPE_MESSAGE) {
                     sb.append("start");
                 } else {
                     sb.append("write");
@@ -2306,7 +2312,7 @@
                 sb.append(" called for field ");
                 sb.append((int)fieldId);
                 sb.append(" which should be used with ");
-                if (fieldType == FIELD_TYPE_OBJECT) {
+                if (fieldType == FIELD_TYPE_MESSAGE) {
                     sb.append("start");
                 } else {
                     sb.append("write");
@@ -2321,7 +2327,7 @@
                 throw new IllegalArgumentException(sb.toString());
             } else {
                 final StringBuilder sb = new StringBuilder();
-                if (expectedType == FIELD_TYPE_OBJECT) {
+                if (expectedType == FIELD_TYPE_MESSAGE) {
                     sb.append("start");
                 } else {
                     sb.append("write");
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 4500862..c44c8dd 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -92,7 +92,7 @@
      * Defines the duration in milliseconds a user needs to hold down the
      * appropriate button to enable the accessibility shortcut once it's configured.
      */
-    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
 
     /**
      * Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index e55d0e4..517ad07 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -130,7 +130,7 @@
      * <p>This method is called when the body of the HTTP response has started loading, is reflected
      * in the DOM, and will be visible in subsequent draws. This callback occurs early in the
      * document loading process, and as such you should expect that linked resources (for example,
-     * css and images) may not be available.
+     * CSS and images) may not be available.
      *
      * <p>For more fine-grained notification of visual state updates, see {@link
      * WebView#postVisualStateCallback}.
@@ -252,7 +252,7 @@
     public static final int ERROR_FILE_NOT_FOUND = -14;
     /** Too many requests during this load */
     public static final int ERROR_TOO_MANY_REQUESTS = -15;
-    /** Resource load was cancelled by Safe Browsing */
+    /** Resource load was canceled by Safe Browsing */
     public static final int ERROR_UNSAFE_RESOURCE = -16;
 
     /** @hide */
@@ -276,8 +276,8 @@
 
     /**
      * Report an error to the host application. These errors are unrecoverable
-     * (i.e. the main resource is unavailable). The errorCode parameter
-     * corresponds to one of the ERROR_* constants.
+     * (i.e. the main resource is unavailable). The {@code errorCode} parameter
+     * corresponds to one of the {@code ERROR_*} constants.
      * @param view The WebView that is initiating the callback.
      * @param errorCode The error code corresponding to an ERROR_* value.
      * @param description A String describing the error.
@@ -293,11 +293,11 @@
     /**
      * Report web resource loading error to the host application. These errors usually indicate
      * inability to connect to the server. Note that unlike the deprecated version of the callback,
-     * the new version will be called for any resource (iframe, image, etc), not just for the main
+     * the new version will be called for any resource (iframe, image, etc.), not just for the main
      * page. Thus, it is recommended to perform minimum required work in this callback.
      * @param view The WebView that is initiating the callback.
      * @param request The originating request.
-     * @param error Information about the error occured.
+     * @param error Information about the error occurred.
      */
     public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
         if (request.isForMainFrame()) {
@@ -310,12 +310,12 @@
     /**
      * Notify the host application that an HTTP error has been received from the server while
      * loading a resource.  HTTP errors have status codes &gt;= 400.  This callback will be called
-     * for any resource (iframe, image, etc), not just for the main page. Thus, it is recommended to
-     * perform minimum required work in this callback. Note that the content of the server
-     * response may not be provided within the <b>errorResponse</b> parameter.
+     * for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
+     * to perform minimum required work in this callback. Note that the content of the server
+     * response may not be provided within the {@code errorResponse} parameter.
      * @param view The WebView that is initiating the callback.
      * @param request The originating request.
-     * @param errorResponse Information about the error occured.
+     * @param errorResponse Information about the error occurred.
      */
     public void onReceivedHttpError(
             WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
@@ -369,7 +369,7 @@
      * if desired and providing the keys. There are three ways to
      * respond: proceed(), cancel() or ignore(). Webview stores the response
      * in memory (for the life of the application) if proceed() or cancel() is
-     * called and does not call onReceivedClientCertRequest() again for the
+     * called and does not call {@code onReceivedClientCertRequest()} again for the
      * same host and port pair. Webview does not store the response if ignore()
      * is called. Note that, multiple layers in chromium network stack might be
      * caching the responses, so the behavior for ignore is only a best case
@@ -436,7 +436,7 @@
     /**
      * Notify the host application that a key was not handled by the WebView.
      * Except system keys, WebView always consumes the keys in the normal flow
-     * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+     * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
      * from where the key is dispatched. It gives the host application a chance
      * to handle the unhandled key events.
      *
@@ -450,7 +450,7 @@
     /**
      * Notify the host application that a input event was not handled by the WebView.
      * Except system keys, WebView always consumes input events in the normal flow
-     * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+     * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
      * from where the event is dispatched. It gives the host application a chance
      * to handle the unhandled input events.
      *
@@ -507,7 +507,7 @@
     }
 
     /**
-     * Notify host application that the given webview's render process has exited.
+     * Notify host application that the given WebView's render process has exited.
      *
      * Multiple WebView instances may be associated with a single render process;
      * onRenderProcessGone will be called for each WebView that was affected.
@@ -517,10 +517,10 @@
      *
      * The given WebView can't be used, and should be removed from the view hierarchy,
      * all references to it should be cleaned up, e.g any references in the Activity
-     * or other classes saved using findViewById and similar calls, etc
+     * or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
      *
      * To cause an render process crash for test purpose, the application can
-     * call loadUrl("chrome://crash") on the WebView. Note that multiple WebView
+     * call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
      * instances may be affected if they share a render process, not just the
      * specific WebView which loaded chrome://crash.
      *
@@ -541,12 +541,13 @@
      * behavior is to show an interstitial to the user, with the reporting checkbox visible.
      *
      * If the application needs to show its own custom interstitial UI, the callback can be invoked
-     * asynchronously with backToSafety() or proceed(), depending on user response.
+     * asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
+     * SafeBrowsingResponse#proceed}, depending on user response.
      *
      * @param view The WebView that hit the malicious resource.
      * @param request Object containing the details of the request.
      * @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
-     *                   SAFE_BROWSING_THREAT_* value.
+     *                   {@code SAFE_BROWSING_THREAT_*} value.
      * @param callback Applications must invoke one of the callback methods.
      */
     public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index 3baccee..05ec9e9 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -106,6 +106,14 @@
     }
 
     /**
+     * Peek the next argument on the command line, whatever it is; if there are
+     * no arguments left, return null.
+     */
+    public String peekNextArg() {
+        return mArgs.peekNextArg();
+    }
+
+    /**
      * Return the next argument on the command line, whatever it is; if there are
      * no arguments left, throws an IllegalArgumentException to report this to the user.
      */
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f0d05da..0535ebe 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -5412,6 +5412,18 @@
         }
     }
 
+    public String[] getWifiIfaces() {
+        synchronized (mWifiNetworkLock) {
+            return mWifiIfaces;
+        }
+    }
+
+    public String[] getMobileIfaces() {
+        synchronized (mModemNetworkLock) {
+            return mModemIfaces;
+        }
+    }
+
     @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
         return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index fb6b8b0..3af3e2a 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.policy;
 
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -99,11 +103,12 @@
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
             boolean isHorizontalDivision, Rect insets) {
-        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, false);
+        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+                DOCKED_INVALID, false);
     }
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isHorizontalDivision, Rect insets, boolean isMinimizedMode) {
+            boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode) {
         mMinFlingVelocityPxPerSecond =
                 MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mMinDismissVelocityPxPerSecond =
@@ -121,7 +126,7 @@
                 com.android.internal.R.dimen.default_minimal_size_resizable_task);
         mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.task_height_of_minimized_mode);
-        calculateTargets(isHorizontalDivision);
+        calculateTargets(isHorizontalDivision, dockSide);
         mFirstSplitTarget = mTargets.get(1);
         mLastSplitTarget = mTargets.get(mTargets.size() - 2);
         mDismissStartTarget = mTargets.get(0);
@@ -254,7 +259,7 @@
         return mTargets.get(minIndex);
     }
 
-    private void calculateTargets(boolean isHorizontalDivision) {
+    private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
         mTargets.clear();
         int dividerMax = isHorizontalDivision
                 ? mDisplayHeight
@@ -273,7 +278,7 @@
                 addMiddleTarget(isHorizontalDivision);
                 break;
             case SNAP_MODE_MINIMIZED:
-                addMinimizedTarget(isHorizontalDivision);
+                addMinimizedTarget(isHorizontalDivision, dockedSide);
                 break;
         }
         mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
@@ -331,12 +336,16 @@
         mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
     }
 
-    private void addMinimizedTarget(boolean isHorizontalDivision) {
+    private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
         // In portrait offset the position by the statusbar height, in landscape add the statusbar
         // height as well to match portrait offset
         int position = mTaskHeightInMinimizedMode + mInsets.top;
         if (!isHorizontalDivision) {
-            position += mInsets.left;
+            if (dockedSide == DOCKED_LEFT) {
+                position += mInsets.left;
+            } else if (dockedSide == DOCKED_RIGHT) {
+                position = mDisplayWidth - position - mInsets.right;
+            }
         }
         mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
     }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f88db25..fb8b9f7 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -185,7 +185,6 @@
         "android/opengl/poly_clip.cpp", // TODO: .arm
         "android/opengl/util.cpp",
         "android_server_NetworkManagementSocketTagger.cpp",
-        "android_server_Watchdog.cpp",
         "android_ddm_DdmHandleNativeHeap.cpp",
         "android_backup_BackupDataInput.cpp",
         "android_backup_BackupDataOutput.cpp",
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index c4f22ee..f6783e1 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -296,7 +296,8 @@
         JNIEnv *env,
         jclass /* clazzObj */,
         jstring ifaceNameObj,
-        jstring serviceNameObj) {
+        jstring serviceNameObj,
+        jboolean retry) {
 
     using ::android::hidl::base::V1_0::IBase;
     using ::android::hardware::details::getRawServiceInternal;
@@ -319,8 +320,7 @@
         serviceName = str.c_str();
     }
 
-    // TODO(b/67981006): true /* retry */
-    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */);
+    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, retry /* retry */, false /* getStub */);
     sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
 
     if (service == NULL) {
@@ -344,6 +344,17 @@
     IPCThreadState::self()->joinThreadPool();
 }
 
+void JHwBinder_native_startRpcThreadPool(JNIEnv *, jclass,
+        jlong maxThreads, jboolean callerWillJoin) {
+    CHECK(maxThreads > 0);
+    ProcessState::self()->setThreadPoolConfiguration(maxThreads,
+                                                     callerWillJoin /* callerJoinsPool */);
+    ssize_t threadsNeeded = maxThreads - (callerWillJoin ? 0 : 1);
+    for (ssize_t i = 0; i < threadsNeeded; ++i) {
+        ProcessState::self()->spawnPooledThread(false /* isMain */);
+    }
+}
+
 static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/)
 {
     report_sysprop_change();
@@ -360,7 +371,7 @@
     { "registerService", "(Ljava/lang/String;)V",
         (void *)JHwBinder_native_registerService },
 
-    { "getService", "(Ljava/lang/String;Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;",
+    { "getService", "(Ljava/lang/String;Ljava/lang/String;Z)L" PACKAGE_PATH "/IHwBinder;",
         (void *)JHwBinder_native_getService },
 
     { "configureRpcThreadpool", "(JZ)V",
@@ -369,6 +380,9 @@
     { "joinRpcThreadpool", "()V",
         (void *)JHwBinder_native_joinRpcThreadpool },
 
+    { "startRpcThreadPool", "(JZ)V",
+        (void *)JHwBinder_native_startRpcThreadPool },
+
     { "native_report_sysprop_change", "()V",
         (void *)JHwBinder_report_sysprop_change },
 };
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index 40d49b7..bb916d2 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -26,6 +26,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/Status.h>
 #include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include "core_jni_helpers.h"
 
@@ -60,12 +61,12 @@
         JNIEnv *env, jobject thiz, const sp<JHwBlob> &context) {
     sp<JHwBlob> old = (JHwBlob *)env->GetLongField(thiz, gFields.contextID);
 
-    if (context != NULL) {
-        context->incStrong(NULL /* id */);
+    if (context != nullptr) {
+        context->incStrong(nullptr /* id */);
     }
 
-    if (old != NULL) {
-        old->decStrong(NULL /* id */);
+    if (old != nullptr) {
+        old->decStrong(nullptr /* id */);
     }
 
     env->SetLongField(thiz, gFields.contextID, (long)context.get());
@@ -150,6 +151,10 @@
     return mBuffer;
 }
 
+void *JHwBlob::data() {
+    return mBuffer;
+}
+
 size_t JHwBlob::size() const {
     return mSize;
 }
@@ -242,8 +247,8 @@
 static void releaseNativeContext(void *nativeContext) {
     sp<JHwBlob> parcel = (JHwBlob *)nativeContext;
 
-    if (parcel != NULL) {
-        parcel->decStrong(NULL /* id */);
+    if (parcel != nullptr) {
+        parcel->decStrong(nullptr /* id */);
     }
 }
 
@@ -313,6 +318,82 @@
     return env->NewStringUTF(s->c_str());
 }
 
+#define DEFINE_BLOB_ARRAY_COPIER(Suffix,Type,NewType)                          \
+static void JHwBlob_native_copyTo ## Suffix ## Array(                          \
+        JNIEnv *env,                                                           \
+        jobject thiz,                                                          \
+        jlong offset,                                                          \
+        Type ## Array array,                                                   \
+        jint size) {                                                           \
+    if (array == nullptr) {                                                    \
+        jniThrowException(env, "java/lang/NullPointerException", nullptr);     \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    if (env->GetArrayLength(array) < size) {                                   \
+        signalExceptionForError(env, BAD_VALUE);                               \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);                   \
+                                                                               \
+    if ((offset + size * sizeof(Type)) > blob->size()) {                       \
+        signalExceptionForError(env, -ERANGE);                                 \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    env->Set ## NewType ## ArrayRegion(                                        \
+            array,                                                             \
+            0 /* start */,                                                     \
+            size,                                                              \
+            reinterpret_cast<const Type *>(                                    \
+                static_cast<const uint8_t *>(blob->data()) + offset));         \
+}
+
+DEFINE_BLOB_ARRAY_COPIER(Int8,jbyte,Byte)
+DEFINE_BLOB_ARRAY_COPIER(Int16,jshort,Short)
+DEFINE_BLOB_ARRAY_COPIER(Int32,jint,Int)
+DEFINE_BLOB_ARRAY_COPIER(Int64,jlong,Long)
+DEFINE_BLOB_ARRAY_COPIER(Float,jfloat,Float)
+DEFINE_BLOB_ARRAY_COPIER(Double,jdouble,Double)
+
+static void JHwBlob_native_copyToBoolArray(
+        JNIEnv *env,
+        jobject thiz,
+        jlong offset,
+        jbooleanArray array,
+        jint size) {
+    if (array == nullptr) {
+        jniThrowException(env, "java/lang/NullPointerException", nullptr);
+        return;
+    }
+
+    if (env->GetArrayLength(array) < size) {
+        signalExceptionForError(env, BAD_VALUE);
+        return;
+    }
+
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+    if ((offset + size * sizeof(bool)) > blob->size()) {
+        signalExceptionForError(env, -ERANGE);
+        return;
+    }
+
+    const bool *src =
+        reinterpret_cast<const bool *>(
+                static_cast<const uint8_t *>(blob->data()) + offset);
+
+    jboolean *dst = env->GetBooleanArrayElements(array, nullptr /* isCopy */);
+
+    for (jint i = 0; i < size; ++i) {
+        dst[i] = src[i];
+    }
+
+    env->ReleaseBooleanArrayElements(array, dst, 0 /* mode */);
+    dst = nullptr;
+}
+
 #define DEFINE_BLOB_PUTTER(Suffix,Type)                                        \
 static void JHwBlob_native_put ## Suffix(                                      \
         JNIEnv *env, jobject thiz, jlong offset, Type x) {                     \
@@ -375,6 +456,59 @@
     blob->putBlob(offset + hidl_string::kOffsetOfBuffer, subBlob);
 }
 
+#define DEFINE_BLOB_ARRAY_PUTTER(Suffix,Type,NewType)                          \
+static void JHwBlob_native_put ## Suffix ## Array(                             \
+        JNIEnv *env, jobject thiz, jlong offset, Type ## Array array) {        \
+    Scoped ## NewType ## ArrayRO autoArray(env, array);                        \
+                                                                               \
+    if (array == nullptr) {                                                    \
+        /* NullpointerException already pending */                             \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);                   \
+                                                                               \
+    status_t err = blob->write(                                                \
+            offset, autoArray.get(), autoArray.size() * sizeof(Type));         \
+                                                                               \
+    if (err != OK) {                                                           \
+        signalExceptionForError(env, err);                                     \
+    }                                                                          \
+}
+
+DEFINE_BLOB_ARRAY_PUTTER(Int8,jbyte,Byte)
+DEFINE_BLOB_ARRAY_PUTTER(Int16,jshort,Short)
+DEFINE_BLOB_ARRAY_PUTTER(Int32,jint,Int)
+DEFINE_BLOB_ARRAY_PUTTER(Int64,jlong,Long)
+DEFINE_BLOB_ARRAY_PUTTER(Float,jfloat,Float)
+DEFINE_BLOB_ARRAY_PUTTER(Double,jdouble,Double)
+
+static void JHwBlob_native_putBoolArray(
+        JNIEnv *env, jobject thiz, jlong offset, jbooleanArray array) {
+    ScopedBooleanArrayRO autoArray(env, array);
+
+    if (array == nullptr) {
+        /* NullpointerException already pending */
+        return;
+    }
+
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+    if ((offset + autoArray.size() * sizeof(bool)) > blob->size()) {
+        signalExceptionForError(env, -ERANGE);
+        return;
+    }
+
+    const jboolean *src = autoArray.get();
+
+    bool *dst = reinterpret_cast<bool *>(
+            static_cast<uint8_t *>(blob->data()) + offset);
+
+    for (size_t i = 0; i < autoArray.size(); ++i) {
+        dst[i] = src[i];
+    }
+}
+
 static void JHwBlob_native_putBlob(
         JNIEnv *env, jobject thiz, jlong offset, jobject blobObj) {
     if (blobObj == nullptr) {
@@ -413,6 +547,14 @@
     { "getDouble", "(J)D", (void *)JHwBlob_native_getDouble },
     { "getString", "(J)Ljava/lang/String;", (void *)JHwBlob_native_getString },
 
+    { "copyToBoolArray", "(J[ZI)V", (void *)JHwBlob_native_copyToBoolArray },
+    { "copyToInt8Array", "(J[BI)V", (void *)JHwBlob_native_copyToInt8Array },
+    { "copyToInt16Array", "(J[SI)V", (void *)JHwBlob_native_copyToInt16Array },
+    { "copyToInt32Array", "(J[II)V", (void *)JHwBlob_native_copyToInt32Array },
+    { "copyToInt64Array", "(J[JI)V", (void *)JHwBlob_native_copyToInt64Array },
+    { "copyToFloatArray", "(J[FI)V", (void *)JHwBlob_native_copyToFloatArray },
+    { "copyToDoubleArray", "(J[DI)V", (void *)JHwBlob_native_copyToDoubleArray },
+
     { "putBool", "(JZ)V", (void *)JHwBlob_native_putBool },
     { "putInt8", "(JB)V", (void *)JHwBlob_native_putInt8 },
     { "putInt16", "(JS)V", (void *)JHwBlob_native_putInt16 },
@@ -422,6 +564,14 @@
     { "putDouble", "(JD)V", (void *)JHwBlob_native_putDouble },
     { "putString", "(JLjava/lang/String;)V", (void *)JHwBlob_native_putString },
 
+    { "putBoolArray", "(J[Z)V", (void *)JHwBlob_native_putBoolArray },
+    { "putInt8Array", "(J[B)V", (void *)JHwBlob_native_putInt8Array },
+    { "putInt16Array", "(J[S)V", (void *)JHwBlob_native_putInt16Array },
+    { "putInt32Array", "(J[I)V", (void *)JHwBlob_native_putInt32Array },
+    { "putInt64Array", "(J[J)V", (void *)JHwBlob_native_putInt64Array },
+    { "putFloatArray", "(J[F)V", (void *)JHwBlob_native_putFloatArray },
+    { "putDoubleArray", "(J[D)V", (void *)JHwBlob_native_putDoubleArray },
+
     { "putBlob", "(JL" PACKAGE_PATH "/HwBlob;)V",
         (void *)JHwBlob_native_putBlob },
 
diff --git a/core/jni/android_os_HwBlob.h b/core/jni/android_os_HwBlob.h
index 39393cb..6b1db63 100644
--- a/core/jni/android_os_HwBlob.h
+++ b/core/jni/android_os_HwBlob.h
@@ -50,6 +50,8 @@
             size_t offset, const android::hardware::hidl_string **s) const;
 
     const void *data() const;
+    void *data();
+
     size_t size() const;
 
     status_t putBlob(size_t offset, const sp<JHwBlob> &blob);
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index cf59a56a..ca5e1e4 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -22,9 +22,13 @@
 
 #include "android_os_HwParcel.h"
 
-#include <nativehelper/JNIHelp.h>
+#include <android/hidl/base/1.0/IBase.h>
+#include <android/hidl/base/1.0/BpHwBase.h>
+#include <android/hidl/base/1.0/BnHwBase.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/Status.h>
+#include <hidl/HidlTransportSupport.h>
+#include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedLocalRef.h>
 
@@ -413,6 +417,44 @@
     return res;
 }
 
+static sp<hidl::base::V1_0::IBase> toIBase(JNIEnv* env, jclass hwRemoteBinderClazz, jobject jbinder)
+{
+    if (jbinder == nullptr) {
+        return nullptr;
+    }
+    if (!env->IsInstanceOf(jbinder, hwRemoteBinderClazz)) {
+        return nullptr;
+    }
+    sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, jbinder);
+    sp<hardware::IBinder> cbinder = context->getBinder();
+    return hardware::fromBinder<hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase,
+                                hidl::base::V1_0::BnHwBase>(cbinder);
+}
+
+// equals iff other is also a non-null android.os.HwRemoteBinder object
+// and getBinder() returns the same object.
+// In particular, if other is an android.os.HwBinder object (for stubs) then
+// it returns false.
+static jboolean JHwRemoteBinder_equals(JNIEnv* env, jobject thiz, jobject other)
+{
+    if (env->IsSameObject(thiz, other)) {
+        return true;
+    }
+    if (other == NULL) {
+        return false;
+    }
+
+    ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
+
+    return hardware::interfacesEqual(toIBase(env, clazz.get(), thiz), toIBase(env, clazz.get(), other));
+}
+
+static jint JHwRemoteBinder_hashCode(JNIEnv* env, jobject thiz) {
+    jlong longHash = reinterpret_cast<jlong>(
+            JHwRemoteBinder::GetNativeContext(env, thiz)->getBinder().get());
+    return static_cast<jint>(longHash ^ (longHash >> 32)); // See Long.hashCode()
+}
+
 static JNINativeMethod gMethods[] = {
     { "native_init", "()J", (void *)JHwRemoteBinder_native_init },
 
@@ -430,6 +472,11 @@
     {"unlinkToDeath",
         "(Landroid/os/IHwBinder$DeathRecipient;)Z",
         (void*)JHwRemoteBinder_unlinkToDeath},
+
+    {"equals", "(Ljava/lang/Object;)Z",
+        (void*)JHwRemoteBinder_equals},
+
+    {"hashCode", "()I", (void*)JHwRemoteBinder_hashCode},
 };
 
 namespace android {
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 5ef2a9e..6243fad 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -102,8 +102,11 @@
         cPackageInfo[i] = cString;
         env->ReleaseStringUTFChars(element, cString);
     }
+    // If we can run this code, the device should already pass AVB.
+    // So, we don't need to check AVB here.
     std::string error;
-    int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error);
+    int32_t status = VintfObject::CheckCompatibility(
+        cPackageInfo, &error, ::android::vintf::DISABLE_AVB_CHECK);
     if (status)
         LOG(WARNING) << "VintfObject.verify() returns " << status << ": " << error;
     return status;
diff --git a/core/jni/android_server_Watchdog.cpp b/core/jni/android_server_Watchdog.cpp
deleted file mode 100644
index 01d565b..0000000
--- a/core/jni/android_server_Watchdog.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- ** Copyright 2010, 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 LOG_TAG "Watchdog_N"
-#include <utils/Log.h>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <string.h>
-#include <errno.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-
-static void dumpOneStack(int tid, int outFd) {
-    char buf[64];
-
-    snprintf(buf, sizeof(buf), "/proc/%d/stack", tid);
-    int stackFd = open(buf, O_RDONLY);
-    if (stackFd >= 0) {
-        // header for readability
-        strncat(buf, ":\n", sizeof(buf) - strlen(buf) - 1);
-        write(outFd, buf, strlen(buf));
-
-        // copy the stack dump text
-        int nBytes;
-        while ((nBytes = read(stackFd, buf, sizeof(buf))) > 0) {
-            write(outFd, buf, nBytes);
-        }
-
-        // footer and done
-        write(outFd, "\n", 1);
-        close(stackFd);
-    } else {
-        ALOGE("Unable to open stack of tid %d : %d (%s)", tid, errno, strerror(errno));
-    }
-}
-
-static void dumpKernelStacks(JNIEnv* env, jobject clazz, jstring pathStr) {
-    char buf[128];
-    DIR* taskdir;
-
-    ALOGI("dumpKernelStacks");
-    if (!pathStr) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Null path");
-        return;
-    }
-
-    const char *path = env->GetStringUTFChars(pathStr, NULL);
-
-    int outFd = open(path, O_WRONLY | O_APPEND | O_CREAT,
-        S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
-    if (outFd < 0) {
-        ALOGE("Unable to open stack dump file: %d (%s)", errno, strerror(errno));
-        goto done;
-    }
-
-    snprintf(buf, sizeof(buf), "\n----- begin pid %d kernel stacks -----\n", getpid());
-    write(outFd, buf, strlen(buf));
-
-    // look up the list of all threads in this process
-    snprintf(buf, sizeof(buf), "/proc/%d/task", getpid());
-    taskdir = opendir(buf);
-    if (taskdir != NULL) {
-        struct dirent * ent;
-        while ((ent = readdir(taskdir)) != NULL) {
-            int tid = atoi(ent->d_name);
-            if (tid > 0 && tid <= 65535) {
-                // dump each stack trace
-                dumpOneStack(tid, outFd);
-            }
-        }
-        closedir(taskdir);
-    }
-
-    snprintf(buf, sizeof(buf), "----- end pid %d kernel stacks -----\n", getpid());
-    write(outFd, buf, strlen(buf));
-
-    close(outFd);
-done:
-    env->ReleaseStringUTFChars(pathStr, path);
-}
-
-// ----------------------------------------
-
-namespace android {
-
-static const JNINativeMethod g_methods[] = {
-    { "native_dumpKernelStacks", "(Ljava/lang/String;)V", (void*)dumpKernelStacks },
-};
-
-int register_android_server_Watchdog(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "com/android/server/Watchdog", g_methods, NELEM(g_methods));
-}
-
-}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 50e811d..e998b09 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -27,6 +27,7 @@
 import "frameworks/base/core/proto/android/os/procrank.proto";
 import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
 import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/server/fingerprint.proto";
 import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
 import "frameworks/base/core/proto/android/service/appwidget.proto";
@@ -135,4 +136,9 @@
         (section).type = SECTION_DUMPSYS,
         (section).args = "alarm --proto"
     ];
+
+    optional com.android.server.wm.proto.WindowManagerServiceProto window = 3017 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "window --proto"
+    ];
 }
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 12649e1..d032a45 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 syntax = "proto2";
+
 option java_multiple_files = true;
 option java_outer_classname = "WakeupSourcesProto";
 
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
 package android.os;
 
 message KernelWakeSources {
@@ -27,6 +29,8 @@
 
 // Next Tag: 11
 message WakeupSourceProto {
+    option (stream_proto.stream).enable_fields_mapping = true;
+
     // Name of the event which triggers application processor
     optional string name = 1;
 
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index f82ea76..22b3d73 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,6 +18,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "PageTypeInfoProto";
 
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
 package android.os;
 
 /*
@@ -61,6 +63,7 @@
 
 // Next tag: 9
 message BlockProto {
+    option (stream_proto.stream).enable_fields_mapping = true;
 
     optional int32 node = 1;
 
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index ab6a6a3..4d62a60 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -18,6 +18,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "ProcrankProto";
 
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
 package android.os;
 
 //Memory usage of running processes
@@ -31,6 +33,8 @@
 
 // Next Tag: 11
 message ProcessProto {
+    option (stream_proto.stream).enable_fields_mapping = true;
+
     // ID of the process
     optional int32 pid = 1;
 
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 064523a..4d48a42 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -15,6 +15,7 @@
  */
 
 syntax = "proto2";
+
 import "frameworks/base/core/proto/android/content/configuration.proto";
 import "frameworks/base/core/proto/android/graphics/rect.proto";
 import "frameworks/base/core/proto/android/view/displayinfo.proto";
diff --git a/core/proto/android/server/windowmanagertrace.proto b/core/proto/android/server/windowmanagertrace.proto
new file mode 100644
index 0000000..0c65bb2
--- /dev/null
+++ b/core/proto/android/server/windowmanagertrace.proto
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/content/configuration.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
+import "frameworks/base/core/proto/android/view/displayinfo.proto";
+import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
+
+package com.android.server.wm.proto;
+
+option java_multiple_files = true;
+
+/* represents a file full of window manager trace entries.
+   Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.WINTRACE), such
+   that they can be easily identified. */
+message WindowManagerTraceFileProto {
+
+    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+        constants into .proto files. */
+    enum MagicNumber {
+        INVALID = 0;
+        MAGIC_NUMBER_L = 0x544e4957;  /* WINT (little-endian ASCII) */
+        MAGIC_NUMBER_H = 0x45434152;  /* RACE (little-endian ASCII) */
+    }
+
+    optional fixed64 magic_number = 1;  /* Must be the first field, set to value in MagicNumber */
+    repeated WindowManagerTraceProto entry = 2;
+}
+
+/* one window manager trace entry. */
+message WindowManagerTraceProto {
+    /* required: elapsed realtime in nanos since boot of when this entry was logged */
+    optional fixed64 elapsed_realtime_nanos = 1;
+
+    /* where the trace originated */
+    optional string where = 2;
+
+    optional WindowManagerServiceProto window_manager_service = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 77bfec3..4a4de24 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3062,10 +3062,10 @@
         android:protectionLevel="signature|privileged|development|appop" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
-    <!-- @hide Allows an application to change the app idle state of an app.
+    <!-- @hide @SystemApi Allows an application to change the app idle state of an app.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
          access the network and acquire wakelocks.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8a84cff..dc68341 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1698,6 +1698,11 @@
          a transaction, so it interacts poorly with SECURE_DELETE. -->
     <string name="db_default_journal_mode" translatable="false">TRUNCATE</string>
 
+    <!-- Enables compatibility WAL mode.
+         In this mode, only database journal mode will be changed, connection pool
+         size will still be limited to a single connection. -->
+    <bool name="db_compatibility_wal_supported">true</bool>
+
     <!-- Maximum size of the persistent journal file in bytes.
          If the journal file grows to be larger than this amount then SQLite will
          truncate it after committing the transaction. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e513816..32758e8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -667,6 +667,7 @@
   <java-symbol type="string" name="date_time" />
   <java-symbol type="string" name="date_time_set" />
   <java-symbol type="string" name="date_time_done" />
+  <java-symbol type="bool" name="db_compatibility_wal_supported" />
   <java-symbol type="string" name="db_default_journal_mode" />
   <java-symbol type="string" name="db_default_sync_mode" />
   <java-symbol type="string" name="db_wal_sync_mode" />
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 115dcb6..0919e82 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -66,6 +66,7 @@
     mHas1BitStencil = extensions.has("GL_OES_stencil1");
     mHas4BitStencil = extensions.has("GL_OES_stencil4");
     mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
+    mHasRenderableFloatTexture = extensions.has("GL_OES_texture_half_float");
 
     mHasSRGB = mVersionMajor >= 3 || extensions.has("GL_EXT_sRGB");
     mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 0ecfdb1..7af7f79 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -38,6 +38,9 @@
     inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; }
     inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; }
     inline bool hasFloatTextures() const { return mVersionMajor >= 3; }
+    inline bool hasRenderableFloatTextures() const {
+        return (mVersionMajor >= 3 && mVersionMinor >= 2) || mHasRenderableFloatTexture;
+    }
     inline bool hasSRGB() const { return mHasSRGB; }
     inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; }
     inline bool hasLinearBlending() const { return hasSRGB() && mHasLinearBlending; }
@@ -56,6 +59,7 @@
     bool mHasSRGB;
     bool mHasSRGBWriteControl;
     bool mHasLinearBlending;
+    bool mHasRenderableFloatTexture;
 
     int mVersionMajor;
     int mVersionMinor;
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index 2687410..751e203 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -128,7 +128,8 @@
         return CopyResult::DestinationInvalid;
     }
 
-    if (bitmap->colorType() == kRGBA_F16_SkColorType && !caches.extensions().hasFloatTextures()) {
+    if (bitmap->colorType() == kRGBA_F16_SkColorType &&
+            !caches.extensions().hasRenderableFloatTextures()) {
         ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
         return CopyResult::DestinationInvalid;
     }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index 608e694..049018cc 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -73,7 +73,7 @@
      * for reading back float buffers (skbug.com/6945).
      */
     if (pixelConfig == kRGBA_half_GrPixelConfig &&
-            !DeviceInfo::get()->extensions().hasFloatTextures()) {
+            !DeviceInfo::get()->extensions().hasRenderableFloatTextures()) {
         ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
         return CopyResult::DestinationInvalid;
     }
diff --git a/libs/hwui/tests/scripts/prep_taieye.sh b/libs/hwui/tests/scripts/prep_taieye.sh
new file mode 100755
index 0000000..503f6d5
--- /dev/null
+++ b/libs/hwui/tests/scripts/prep_taieye.sh
@@ -0,0 +1,50 @@
+nr=$(adb shell cat /proc/cpuinfo | grep processor | wc -l)
+cpubase=/sys/devices/system/cpu
+
+adb root
+adb wait-for-device
+adb shell stop vendor.perfd
+adb shell stop thermal-engine
+
+S=1036800
+cpu=0
+# Changing governor and frequency in one core will be automatically applied
+# to other cores in the cluster
+while [ $((cpu < 4)) -eq 1 ]; do
+    echo "Setting cpu ${cpu} to $S hz"
+    adb shell "echo userspace > $cpubase/cpu${cpu}/cpufreq/scaling_governor"
+    adb shell "echo 1 > $cpubase/cpu${cpu}/online"
+    adb shell "echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_max_freq"
+    adb shell "echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_min_freq"
+    cpu=$(($cpu + 1))
+done
+
+while [ $((cpu < $nr)) -eq 1 ]; do
+  echo "disable cpu $cpu"
+  adb shell "echo 0 > $cpubase/cpu${cpu}/online"
+  cpu=$(($cpu + 1))
+done
+
+echo "setting GPU bus and idle timer"
+adb shell "echo 0 > /sys/class/kgsl/kgsl-3d0/bus_split"
+adb shell "echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on"
+adb shell "echo 10000 > /sys/class/kgsl/kgsl-3d0/idle_timer"
+
+#0 762 1144 1525 2288 3143 4173 5195 5859 7759 9887 11863 13763
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,gpubw/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,gpubw/max_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,cpubw/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,cpubw/max_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,mincpubw/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,mincpubw/max_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,memlat-cpu0/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,memlat-cpu0/max_freq"
+
+# 180000000 257000000 342000000 414000000 515000000 596000000 670000000 710000000
+echo "performance mode, 342 MHz"
+adb shell "echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor"
+adb shell "echo 342000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq"
+adb shell "echo 342000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq"
+
+adb shell "echo 4 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel"
+adb shell "echo 4 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel"
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
new file mode 100644
index 0000000..4f1d2d5
--- /dev/null
+++ b/libs/protoutil/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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.
+
+cc_library {
+    name: "libprotoutil",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-unused-variable",
+        "-Wunused-parameter",
+    ],
+
+    srcs: [
+        "src/EncodedBuffer.cpp",
+        "src/ProtoOutputStream.cpp",
+        "src/protobuf.cpp",
+    ],
+
+    export_include_dirs: ["include"],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+}
diff --git a/libs/protoutil/Android.mk b/libs/protoutil/Android.mk
deleted file mode 100644
index 2a2b087..0000000
--- a/libs/protoutil/Android.mk
+++ /dev/null
@@ -1,39 +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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libprotoutil
-
-LOCAL_CFLAGS := \
-        -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
-
-LOCAL_SHARED_LIBRARIES := \
-        libcutils \
-        liblog \
-
-LOCAL_C_INCLUDES := \
-        $(LOCAL_PATH)/include
-
-LOCAL_SRC_FILES := \
-        src/EncodedBuffer.cpp \
-        src/ProtoOutputStream.cpp \
-        src/protobuf.cpp \
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-include $(BUILD_SHARED_LIBRARY)
-
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index b8415b2..ce41849 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -19,13 +19,63 @@
 
 #include <android/util/EncodedBuffer.h>
 
-#include <stdint.h>
 #include <string>
 
 namespace android {
 namespace util {
 
 /**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+/**
+ * Mask for the field types stored in a fieldId.  Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
+
+/**
+ * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+ * so no extra mapping needs to be maintained in this case.
+ */
+const uint64_t FIELD_TYPE_UNKNOWN  = 0;
+const uint64_t FIELD_TYPE_DOUBLE   = 1ULL << FIELD_TYPE_SHIFT;   // double, exactly eight bytes on the wire.
+const uint64_t FIELD_TYPE_FLOAT    = 2ULL << FIELD_TYPE_SHIFT;   // float, exactly four bytes on the wire.
+const uint64_t FIELD_TYPE_INT64    = 3ULL << FIELD_TYPE_SHIFT;   // int64, varint on the wire.  Negative numbers
+                                                                 // take 10 bytes.  Use TYPE_SINT64 if negative
+                                                                 // values are likely.
+const uint64_t FIELD_TYPE_UINT64   = 4ULL << FIELD_TYPE_SHIFT;   // uint64, varint on the wire.
+const uint64_t FIELD_TYPE_INT32    = 5ULL << FIELD_TYPE_SHIFT;   // int32, varint on the wire.  Negative numbers
+                                                                 // take 10 bytes.  Use TYPE_SINT32 if negative
+                                                                 // values are likely.
+const uint64_t FIELD_TYPE_FIXED64  = 6ULL << FIELD_TYPE_SHIFT;   // uint64, exactly eight bytes on the wire.
+const uint64_t FIELD_TYPE_FIXED32  = 7ULL << FIELD_TYPE_SHIFT;   // uint32, exactly four bytes on the wire.
+const uint64_t FIELD_TYPE_BOOL     = 8ULL << FIELD_TYPE_SHIFT;   // bool, varint on the wire.
+const uint64_t FIELD_TYPE_STRING   = 9ULL << FIELD_TYPE_SHIFT;   // UTF-8 text.
+// const uint64_t FIELD_TYPE_GROUP = 10ULL << FIELD_TYPE_SHIFT;  // Tag-delimited message.  Deprecated.
+const uint64_t FIELD_TYPE_MESSAGE  = 11ULL << FIELD_TYPE_SHIFT;  // Length-delimited message.
+
+const uint64_t FIELD_TYPE_BYTES    = 12ULL << FIELD_TYPE_SHIFT;  // Arbitrary byte array.
+const uint64_t FIELD_TYPE_UINT32   = 13ULL << FIELD_TYPE_SHIFT;  // uint32, varint on the wire
+const uint64_t FIELD_TYPE_ENUM     = 14ULL << FIELD_TYPE_SHIFT;  // Enum, varint on the wire
+const uint64_t FIELD_TYPE_SFIXED32 = 15ULL << FIELD_TYPE_SHIFT;  // int32, exactly four bytes on the wire
+const uint64_t FIELD_TYPE_SFIXED64 = 16ULL << FIELD_TYPE_SHIFT;  // int64, exactly eight bytes on the wire
+const uint64_t FIELD_TYPE_SINT32   = 17ULL << FIELD_TYPE_SHIFT;  // int32, ZigZag-encoded varint on the wire
+const uint64_t FIELD_TYPE_SINT64   = 18ULL << FIELD_TYPE_SHIFT;  // int64, ZigZag-encoded varint on the wire
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5ULL << FIELD_COUNT_SHIFT;
+
+/**
  * Class to write to a protobuf stream.
  *
  * Each write method takes an ID code from the protoc generated classes
@@ -60,11 +110,12 @@
     void end(long long token);
 
     /**
-     * Flushes the protobuf data out to given fd.
+     * Flushes the protobuf data out to given fd. When the following functions are called,
+     * it is not able to write to ProtoOutputStream any more since the data is compact.
      */
-    size_t size();
-    EncodedBuffer::iterator data();
-    bool flush(int fd);
+    size_t size(); // Get the size of the serialized protobuf.
+    EncodedBuffer::iterator data(); // Get the reader apis of the data.
+    bool flush(int fd); // Flush data directly to a file descriptor.
 
     // Please don't use the following functions to dump protos unless you are familiar with protobuf encoding.
     void writeRawVarint(uint64_t varint);
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index b91e3db0..9d8ee729 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -18,58 +18,10 @@
 #include <android/util/protobuf.h>
 #include <android/util/ProtoOutputStream.h>
 #include <cutils/log.h>
-#include <cstring>
 
 namespace android {
 namespace util {
 
-/**
- * Position of the field type in a (long long) fieldId.
- */
-const uint64_t FIELD_TYPE_SHIFT = 32;
-
-/**
- * Mask for the field types stored in a fieldId.  Leaves a whole
- * byte for future expansion, even though there are currently only 17 types.
- */
-const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
-
-const uint64_t FIELD_TYPE_UNKNOWN  = 0;
-const uint64_t TYPE_DOUBLE         = 1ULL << FIELD_TYPE_SHIFT;   // double, exactly eight bytes on the wire.
-const uint64_t TYPE_FLOAT          = 2ULL << FIELD_TYPE_SHIFT;   // float, exactly four bytes on the wire.
-const uint64_t TYPE_INT64          = 3ULL << FIELD_TYPE_SHIFT;   // int64, varint on the wire.  Negative numbers
-                                                                 // take 10 bytes.  Use TYPE_SINT64 if negative
-                                                                 // values are likely.
-const uint64_t TYPE_UINT64         = 4ULL << FIELD_TYPE_SHIFT;   // uint64, varint on the wire.
-const uint64_t TYPE_INT32          = 5ULL << FIELD_TYPE_SHIFT;   // int32, varint on the wire.  Negative numbers
-                                                                 // take 10 bytes.  Use TYPE_SINT32 if negative
-                                                                 // values are likely.
-const uint64_t TYPE_FIXED64        = 6ULL << FIELD_TYPE_SHIFT;   // uint64, exactly eight bytes on the wire.
-const uint64_t TYPE_FIXED32        = 7ULL << FIELD_TYPE_SHIFT;   // uint32, exactly four bytes on the wire.
-const uint64_t TYPE_BOOL           = 8ULL << FIELD_TYPE_SHIFT;   // bool, varint on the wire.
-const uint64_t TYPE_STRING         = 9ULL << FIELD_TYPE_SHIFT;   // UTF-8 text.
-const uint64_t TYPE_GROUP          = 10ULL << FIELD_TYPE_SHIFT;  // Tag-delimited message.  Deprecated.
-const uint64_t TYPE_MESSAGE        = 11ULL << FIELD_TYPE_SHIFT;  // Length-delimited message.
-
-const uint64_t TYPE_BYTES          = 12ULL << FIELD_TYPE_SHIFT;  // Arbitrary byte array.
-const uint64_t TYPE_UINT32         = 13ULL << FIELD_TYPE_SHIFT;  // uint32, varint on the wire
-const uint64_t TYPE_ENUM           = 14ULL << FIELD_TYPE_SHIFT;  // Enum, varint on the wire
-const uint64_t TYPE_SFIXED32       = 15ULL << FIELD_TYPE_SHIFT;  // int32, exactly four bytes on the wire
-const uint64_t TYPE_SFIXED64       = 16ULL << FIELD_TYPE_SHIFT;  // int64, exactly eight bytes on the wire
-const uint64_t TYPE_SINT32         = 17ULL << FIELD_TYPE_SHIFT;  // int32, ZigZag-encoded varint on the wire
-const uint64_t TYPE_SINT64         = 18ULL << FIELD_TYPE_SHIFT;  // int64, ZigZag-encoded varint on the wire
-
-//
-// FieldId flags for whether the field is single, repeated or packed.
-// TODO: packed is not supported yet.
-//
-const uint64_t FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_UNKNOWN = 0;
-const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
-
 ProtoOutputStream::ProtoOutputStream()
         :mBuffer(),
          mCopyBegin(0),
@@ -90,18 +42,18 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
         default:
             ALOGW("Field type %d is not supported when writing double val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -116,18 +68,18 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
         default:
             ALOGW("Field type %d is not supported when writing float val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -142,20 +94,20 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
-        case TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
-        case TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case FIELD_TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
         default:
             ALOGW("Field type %d is not supported when writing int val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -170,20 +122,20 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
-        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
-        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
-        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
-        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
-        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
-        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
-        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
-        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
-        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
-        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
-        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
-        case TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
-        case TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case FIELD_TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
         default:
             ALOGW("Field type %d is not supported when writing long long val.",
                     (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -198,7 +150,7 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_BOOL:
+        case FIELD_TYPE_BOOL:
             writeBoolImpl(id, val);
             return true;
         default:
@@ -214,7 +166,7 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_STRING:
+        case FIELD_TYPE_STRING:
             writeUtf8StringImpl(id, val.c_str(), val.size());
             return true;
         default:
@@ -230,11 +182,11 @@
     if (mCompact) return false;
     const uint32_t id = (uint32_t)fieldId;
     switch (fieldId & FIELD_TYPE_MASK) {
-        case TYPE_STRING:
-        case TYPE_BYTES:
+        case FIELD_TYPE_STRING:
+        case FIELD_TYPE_BYTES:
             writeUtf8StringImpl(id, val, size);
             return true;
-        case TYPE_MESSAGE:
+        case FIELD_TYPE_MESSAGE:
             // can directly write valid format of message bytes into ProtoOutputStream without calling start/end
             writeMessageBytesImpl(id, val, size);
             return true;
@@ -291,7 +243,7 @@
 long long
 ProtoOutputStream::start(uint64_t fieldId)
 {
-    if ((fieldId & FIELD_TYPE_MASK) != TYPE_MESSAGE) {
+    if ((fieldId & FIELD_TYPE_MASK) != FIELD_TYPE_MESSAGE) {
         ALOGE("Can't call start for non-message type field: 0x%llx", (long long)fieldId);
         return 0;
     }
@@ -564,7 +516,6 @@
 inline void
 ProtoOutputStream::writeDoubleImpl(uint32_t id, double val)
 {
-    if (val == 0.0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
     mBuffer.writeRawFixed64(bit_cast<double, uint64_t>(val));
 }
@@ -572,7 +523,6 @@
 inline void
 ProtoOutputStream::writeFloatImpl(uint32_t id, float val)
 {
-    if (val == 0.0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
     mBuffer.writeRawFixed32(bit_cast<float, uint32_t>(val));
 }
@@ -580,7 +530,6 @@
 inline void
 ProtoOutputStream::writeInt64Impl(uint32_t id, long long val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint64((uint64_t)val);
 }
@@ -588,7 +537,6 @@
 inline void
 ProtoOutputStream::writeInt32Impl(uint32_t id, int val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32((uint32_t)val);
 }
@@ -596,7 +544,6 @@
 inline void
 ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val)
 {
-   if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint64(val);
 }
@@ -604,7 +551,6 @@
 inline void
 ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32(val);
 }
@@ -612,7 +558,6 @@
 inline void
 ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
     mBuffer.writeRawFixed64(val);
 }
@@ -620,7 +565,6 @@
 inline void
 ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
     mBuffer.writeRawFixed32(val);
 }
@@ -628,7 +572,6 @@
 inline void
 ProtoOutputStream::writeSFixed64Impl(uint32_t id, long long val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
     mBuffer.writeRawFixed64((uint64_t)val);
 }
@@ -636,7 +579,6 @@
 inline void
 ProtoOutputStream::writeSFixed32Impl(uint32_t id, int val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
     mBuffer.writeRawFixed32((uint32_t)val);
 }
@@ -644,7 +586,6 @@
 inline void
 ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, long long val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint64((val << 1) ^ (val >> 63));
 }
@@ -652,7 +593,6 @@
 inline void
 ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int val)
 {
-    if (val == 0) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32((val << 1) ^ (val >> 31));
 }
@@ -667,7 +607,6 @@
 inline void
 ProtoOutputStream::writeBoolImpl(uint32_t id, bool val)
 {
-    if (!val) return;
     mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
     mBuffer.writeRawVarint32(val ? 1 : 0);
 }
@@ -675,7 +614,7 @@
 inline void
 ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size)
 {
-    if (val == NULL || size == 0) return;
+    if (val == NULL) return;
     writeLengthDelimitedHeader(id, size);
     for (size_t i=0; i<size; i++) {
         mBuffer.writeRawByte((uint8_t)val[i]);
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index e7f903e..c9d2f7f 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -821,7 +821,7 @@
      * considered 1 standard deviation.
      *
      * <p>For example, if {@link #getAltitude()} returns 150, and
-     * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+     * {@link #getVerticalAccuracyMeters()} returns 20 then there is a 68% probability
      * of the true altitude being between 130 and 170 meters.
      *
      * <p>If this location does not have a vertical accuracy, then 0.0 is returned.
@@ -933,7 +933,7 @@
      * considered 1 standard deviation.
      *
      * <p>For example, if {@link #getBearing()} returns 60, and
-     * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+     * {@link #getBearingAccuracyDegrees()} returns 10, then there is a 68% probability of the
      * true bearing being between 50 and 70 degrees.
      *
      * <p>If this location does not have a bearing accuracy, then 0.0 is returned.
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index ba41a7b..9175416 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2564,51 +2564,66 @@
                 });
             }
 
+            String hasImage = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
             String hasVideo = retriever.extractMetadata(
                     MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
 
-            final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
-            if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
-                String width = retriever.extractMetadata(
+            String width = null;
+            String height = null;
+            String rotation = null;
+            final String METADATA_VALUE_YES = "yes";
+            // If the file has both image and video, prefer image info over video info.
+            // App querying ExifInterface is most likely using the bitmap path which
+            // picks the image first.
+            if (METADATA_VALUE_YES.equals(hasImage)) {
+                width = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+                height = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+                rotation = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+            } else if (METADATA_VALUE_YES.equals(hasVideo)) {
+                width = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-                String height = retriever.extractMetadata(
+                height = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-
-                if (width != null) {
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
-                            ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
-                }
-
-                if (height != null) {
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
-                            ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
-                }
-
-                String rotation = retriever.extractMetadata(
+                rotation = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
-                if (rotation != null) {
-                    int orientation = ExifInterface.ORIENTATION_NORMAL;
+            }
 
-                    // all rotation angles in CW
-                    switch (Integer.parseInt(rotation)) {
-                        case 90:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
-                            break;
-                        case 180:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_180;
-                            break;
-                        case 270:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
-                            break;
-                    }
+            if (width != null) {
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+                        ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
+            }
 
-                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
-                            ExifAttribute.createUShort(orientation, mExifByteOrder));
+            if (height != null) {
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+                        ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
+            }
+
+            if (rotation != null) {
+                int orientation = ExifInterface.ORIENTATION_NORMAL;
+
+                // all rotation angles in CW
+                switch (Integer.parseInt(rotation)) {
+                    case 90:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                        break;
+                    case 180:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_180;
+                        break;
+                    case 270:
+                        orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                        break;
                 }
 
-                if (DEBUG) {
-                    Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
-                }
+                mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+                        ExifAttribute.createUShort(orientation, mExifByteOrder));
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
             }
         } finally {
             retriever.release();
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index c2167f3..4658924 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -33,19 +33,17 @@
         android:background="?android:attr/selectableItemBackground"
         android:gravity="start|center_vertical"
         android:clipToPadding="false"
-        android:layout_marginStart="4dp"
         android:paddingStart="?android:attr/listPreferredItemPaddingStart"
         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
         <LinearLayout
             android:id="@+id/icon_frame"
-            style="@style/preference_icon_frame"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:gravity="start|center_vertical"
+            android:minWidth="56dp"
             android:orientation="horizontal"
             android:clipToPadding="false"
-            android:paddingEnd="12dp"
             android:paddingTop="4dp"
             android:paddingBottom="4dp">
             <android.support.v7.internal.widget.PreferenceImageView
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index b7ea1d4..3f312f4 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -21,9 +21,4 @@
     <style name="TextAppearanceMedium">
         <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
     </style>
-
-    <style name="preference_icon_frame">
-        <item name="android:layout_marginStart">-4dp</item>
-        <item name="android:minWidth">60dp</item>
-    </style>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index 32e6389..c3a36e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
@@ -343,7 +344,8 @@
         }
         DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
-        if (dpm == null) {
+        PackageManager pm = context.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) || dpm == null) {
             return null;
         }
         boolean isAccountTypeDisabled = false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 6aae226..3c02f6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -16,11 +16,13 @@
 
 package com.android.settingslib.development;
 
+import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
 import android.support.v14.preference.SwitchPreference;
 import android.support.v4.content.LocalBroadcastManager;
 import android.support.v7.preference.Preference;
@@ -95,6 +97,10 @@
 
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
+        if (isUserAMonkey()) {
+            return false;
+        }
+
         if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) {
             if (!isAdbEnabled()) {
                 showConfirmationDialog(preference);
@@ -117,4 +123,9 @@
         LocalBroadcastManager.getInstance(mContext)
                 .sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
     }
+
+    @VisibleForTesting
+    boolean isUserAMonkey() {
+        return ActivityManager.isUserAMonkey();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index ca366ea..64de635 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -33,6 +33,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 
@@ -56,6 +57,8 @@
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private RestrictedLockUtils.Proxy mProxy;
 
@@ -72,11 +75,32 @@
                 .thenReturn(mDevicePolicyManager);
         when(mContext.getSystemService(Context.USER_SERVICE))
                 .thenReturn(mUserManager);
+        when(mContext.getPackageManager())
+                .thenReturn(mPackageManager);
 
         RestrictedLockUtils.sProxy = mProxy;
     }
 
     @Test
+    public void checkIfDevicePolicyServiceDisabled_noEnforceAdminForManagedProfile() {
+        when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(null);
+        final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfAccountManagementDisabled(
+                mContext, "account_type", mUserId);
+
+        assertThat(enforcedAdmin).isEqualTo(null);
+    }
+
+    @Test
+    public void checkIfDeviceAdminFeatureDisabled_noEnforceAdminForManagedProfile() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
+                .thenReturn(false);
+        final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfAccountManagementDisabled(
+                mContext, "account_type", mUserId);
+
+        assertThat(enforcedAdmin).isEqualTo(null);
+    }
+
+    @Test
     public void checkIfKeyguardFeaturesDisabled_noEnforcedAdminForManagedProfile() {
         setUpManagedProfile(mUserId, new ComponentName[] {mAdmin1, mAdmin2});
 
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 bcabff3..32fa01c 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
@@ -21,6 +21,7 @@
 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;
@@ -119,6 +120,18 @@
     }
 
     @Test
+    public void handlePreferenceTreeClick_isMonkeyUser_shouldBeFalse() {
+        mController = spy(mController);
+        doReturn(true).when(mController).isUserAMonkey();
+        when(mUserManager.isAdminUser()).thenReturn(true);
+        mController.displayPreference(mScreen);
+
+        final boolean handled = mController.handlePreferenceTreeClick(mPreference);
+
+        assertThat(handled).isFalse();
+    }
+
+    @Test
     public void updateState_settingsOn_shouldCheck() {
         when(mUserManager.isAdminUser()).thenReturn(true);
         Settings.Secure.putInt(mContext.getContentResolver(),
@@ -161,6 +174,7 @@
         }
 
         @Override
-        public void dismissConfirmationDialog() {}
+        public void dismissConfirmationDialog() {
+        }
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
new file mode 100644
index 0000000..a46fab3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
@@ -0,0 +1,84 @@
+/*
+ * 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.shared.recents.view;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+/**
+ * To be implemented by a particular animation to asynchronously provide the animation specs for a
+ * particular transition.
+ */
+public abstract class AppTransitionAnimationSpecsFuture {
+
+    private final Handler mHandler;
+    private FutureTask<List<AppTransitionAnimationSpec>> mComposeTask = new FutureTask<>(() -> {
+        synchronized (AppTransitionAnimationSpecsFuture.this) {
+            return composeSpecs();
+        }
+    });
+
+    private final IAppTransitionAnimationSpecsFuture mFuture =
+            new IAppTransitionAnimationSpecsFuture.Stub() {
+        @Override
+        public AppTransitionAnimationSpec[] get() throws RemoteException {
+            try {
+                if (!mComposeTask.isDone()) {
+                    mHandler.post(mComposeTask);
+                }
+                List<AppTransitionAnimationSpec> specs = mComposeTask.get();
+                if (specs == null) {
+                    return null;
+                }
+
+                AppTransitionAnimationSpec[] arr = new AppTransitionAnimationSpec[specs.size()];
+                specs.toArray(arr);
+                return arr;
+            } catch (Exception e) {
+                return null;
+            }
+        }
+    };
+
+    public AppTransitionAnimationSpecsFuture(Handler handler) {
+        mHandler = handler;
+    }
+
+    /**
+     * Returns the future to handle the call from window manager.
+     */
+    public final IAppTransitionAnimationSpecsFuture getFuture() {
+        return mFuture;
+    }
+
+    /**
+     * Called ahead of the future callback to compose the specs to be returned in the future.
+     */
+    public final void composeSpecsSynchronous() {
+        if (Looper.myLooper() != mHandler.getLooper()) {
+            throw new RuntimeException("composeSpecsSynchronous() called from wrong looper");
+        }
+        mComposeTask.run();
+    }
+
+    public abstract List<AppTransitionAnimationSpec> composeSpecs();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
new file mode 100644
index 0000000..3095d15
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
@@ -0,0 +1,117 @@
+/*
+ * 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.shared.recents.view;
+
+import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+import java.util.function.Consumer;
+
+/**
+ * A helper class to create transitions to/from an App to Recents.
+ */
+public class RecentsTransition {
+
+    /**
+     * Creates a new transition aspect scaled transition activity options.
+     */
+    public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler,
+            boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture,
+            OnAnimationStartedListener animationStartCallback) {
+        final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() {
+            private boolean mHandled;
+
+            @Override
+            public void onAnimationStarted() {
+                // OnAnimationStartedListener can be called numerous times, so debounce here to
+                // prevent multiple callbacks
+                if (mHandled) {
+                    return;
+                }
+                mHandled = true;
+
+                if (animationStartCallback != null) {
+                    animationStartCallback.onAnimationStarted();
+                }
+            }
+        };
+        final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(
+                context, handler,
+                animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null,
+                animStartedListener, scaleUp);
+        return opts;
+    }
+
+    /**
+     * Wraps a animation-start callback in a binder that can be called from window manager.
+     */
+    public static IRemoteCallback wrapStartedListener(Handler handler,
+            OnAnimationStartedListener listener) {
+        if (listener == null) {
+            return null;
+        }
+        return new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) throws RemoteException {
+                handler.post(listener::onAnimationStarted);
+            }
+        };
+    }
+
+    /**
+     * @return a {@link GraphicBuffer} with the {@param view} drawn into it. Result can be null if
+     *         we were unable to allocate a hardware bitmap.
+     */
+    public static GraphicBuffer drawViewIntoGraphicBuffer(int width, int height, View view,
+            float scale, int eraseColor) {
+        final Bitmap hwBitmap = createHardwareBitmap(width, height, (c) -> {
+            c.scale(scale, scale);
+            if (eraseColor != 0) {
+                c.drawColor(eraseColor);
+            }
+            if (view != null) {
+                view.draw(c);
+            }
+        });
+        return hwBitmap != null ? hwBitmap.createGraphicBufferHandle() : null;
+    }
+
+    /**
+     * @return a hardware {@link Bitmap} after being drawn with the {@param consumer}. Result can be
+     *         null if we were unable to allocate a hardware bitmap.
+     */
+    public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) {
+        RenderNode node = RenderNode.create("RecentsTransition", null);
+        node.setLeftTopRightBottom(0, 0, width, height);
+        node.setClipToBounds(false);
+        DisplayListCanvas c = node.start(width, height);
+        consumer.accept(c);
+        node.end(c);
+        return ThreadedRenderer.createHardwareBitmap(node, width, height);
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 3f93f76..090617d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -50,11 +50,13 @@
 
     private final PackageManager mPackageManager;
     private final IconDrawableFactory mDrawableFactory;
+    private final BackgroundExecutor mBackgroundExecutor;
 
     private ActivityManagerWrapper() {
         final Context context = AppGlobals.getInitialApplication();
         mPackageManager = context.getPackageManager();
         mDrawableFactory = IconDrawableFactory.newInstance(context);
+        mBackgroundExecutor = BackgroundExecutor.get();
     }
 
     public static ActivityManagerWrapper getInstance() {
@@ -198,4 +200,52 @@
         }
         return label;
     }
+
+    /**
+     * Requests that the system close any open system windows (including other SystemUI).
+     */
+    public void closeSystemWindows(String reason) {
+        mBackgroundExecutor.submit(() -> {
+            try {
+                ActivityManager.getService().closeSystemDialogs(reason);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to close system windows", e);
+            }
+        });
+    }
+
+    /**
+     * Removes a task by id.
+     */
+    public void removeTask(int taskId) {
+        mBackgroundExecutor.submit(() -> {
+            try {
+                ActivityManager.getService().removeTask(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to remove task=" + taskId, e);
+            }
+        });
+    }
+
+    /**
+     * Cancels the current window transtion to/from Recents for the given task id.
+     */
+    public void cancelWindowTransition(int taskId) {
+        try {
+            ActivityManager.getService().cancelTaskWindowTransition(taskId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+        }
+    }
+
+    /**
+     * Cancels the current thumbnail transtion to/from Recents for the given task id.
+     */
+    public void cancelThumbnailTransition(int taskId) {
+        try {
+            ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+        }
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java
new file mode 100644
index 0000000..26e1813
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -0,0 +1,45 @@
+/*
+ * 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.shared.system;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Offloads work from other threads by running it in a background thread.
+ */
+public class BackgroundExecutor {
+
+    private static final BackgroundExecutor sInstance = new BackgroundExecutor();
+
+    private final ExecutorService mExecutorService = Executors.newFixedThreadPool(2);
+
+    /**
+     * @return the static instance of the background executor.
+     */
+    public static BackgroundExecutor get() {
+        return sInstance;
+    }
+
+    /**
+     * Runs the given {@param runnable} on one of the background executor threads.
+     */
+    public Future<?> submit(Runnable runnable) {
+        return mExecutorService.submit(runnable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 6d0952a..36310f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -14,24 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
 
 import android.app.ActivityManager.TaskSnapshot;
-import android.content.Context;
 import android.os.UserHandle;
 import android.util.Log;
 
 /**
- * An abstract class to track task stack changes.
- * Classes should implement this instead of {@link android.app.ITaskStackListener}
- * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ * An interface to track task stack changes. Classes should implement this instead of
+ * {@link android.app.ITaskStackListener} to reduce IPC calls from system services.
  */
 public abstract class TaskStackChangeListener {
 
-    /**
-     * NOTE: This call is made of the thread that the binder call comes in on.
-     */
+    // Binder thread callbacks
     public void onTaskStackChangedBackground() { }
+
+    // Main thread callbacks
     public void onTaskStackChanged() { }
     public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
@@ -45,17 +43,16 @@
     public void onTaskProfileLocked(int taskId, int userId) { }
 
     /**
-     * Checks that the current user matches the user's SystemUI process. Since
+     * Checks that the current user matches the process. Since
      * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
-     * TaskStackChangeListener should make this call to verify that we don't act on events from other
-     * user's processes.
+     * {@link TaskStackChangeListener} should make this call to verify that we don't act on events
+     * originating from another user's interactions.
      */
-    protected final boolean checkCurrentUserId(Context context, boolean debug) {
+    protected final boolean checkCurrentUserId(int currentUserId, boolean debug) {
         int processUserId = UserHandle.myUserId();
-        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
         if (processUserId != currentUserId) {
             if (debug) {
-                Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+                Log.d("TaskStackChangeListener", "UID mismatch. Process is uid=" + processUserId
                         + " and the current user is uid=" + currentUserId);
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 8eb70f0..95fc96f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
 
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.IActivityManager;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 282a71b..623fe53 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -76,8 +76,8 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import com.google.android.collect.Lists;
 
@@ -1776,7 +1776,8 @@
         }
     }
 
-    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    private final SysUiTaskStackChangeListener
+            mTaskStackListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskStackChangedBackground() {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 4c3d5ba..4119ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -26,14 +26,16 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 public class WorkLockActivityController {
+    private static final String TAG = WorkLockActivityController.class.getSimpleName();
+
     private final Context mContext;
-    private final SystemServicesProxy mSsp;
     private final IActivityManager mIam;
 
     public WorkLockActivityController(Context context) {
@@ -43,17 +45,22 @@
     @VisibleForTesting
     WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) {
         mContext = context;
-        mSsp = ssp;
         mIam = am;
 
-        mSsp.registerTaskStackListener(mLockListener);
+        ssp.registerTaskStackListener(mLockListener);
     }
 
     private void startWorkChallengeInTask(int taskId, int userId) {
+        ActivityManager.TaskDescription taskDescription = null;
+        try {
+            taskDescription = mIam.getTaskDescription(taskId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to get description for task=" + taskId);
+        }
         Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
                 .setComponent(new ComponentName(mContext, WorkLockActivity.class))
                 .putExtra(Intent.EXTRA_USER_ID, userId)
-                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
+                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
                 .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
@@ -67,7 +74,11 @@
         } else {
             // Starting the activity inside the task failed. We can't be sure why, so to be
             // safe just remove the whole task if it still exists.
-            mSsp.removeTask(taskId);
+            try {
+                mIam.removeTask(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to get description for task=" + taskId);
+            }
         }
     }
 
@@ -96,7 +107,7 @@
         }
     }
 
-    private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
+    private final SysUiTaskStackChangeListener mLockListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskProfileLocked(int taskId, int userId) {
             startWorkChallengeInTask(taskId, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 7e87666..7ef0f15 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -40,8 +40,9 @@
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.PrintWriter;
 
@@ -69,7 +70,7 @@
     /**
      * Handler for system task stack changes.
      */
-    TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             mTouchHandler.onActivityPinned();
@@ -202,9 +203,9 @@
                 StackInfo stackInfo = mActivityManager.getStackInfo(
                         WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (stackInfo != null && stackInfo.taskIds != null) {
-                    SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
+                    ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
                     for (int taskId : stackInfo.taskIds) {
-                        ssp.cancelThumbnailTransition(taskId);
+                        am.cancelThumbnailTransition(taskId);
                     }
                 }
             } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index c92562b..5a91def 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -45,8 +45,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -620,7 +620,7 @@
         return false;
     }
 
-    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    private SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 2250e41..61c5167 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -63,6 +63,7 @@
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -246,7 +247,7 @@
             return;
         }
 
-        sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
         int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b75a142..c666d57 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents;
 
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
@@ -92,6 +95,7 @@
 import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
@@ -280,8 +284,7 @@
                 new DismissRecentsToHomeAnimationStarted(animateTaskViews);
         dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
                 overrideAnimation));
-        Recents.getSystemServices().sendCloseSystemWindows(
-                StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
         EventBus.getDefault().send(dismissEvent);
     }
 
@@ -706,9 +709,9 @@
         int launchToTaskId = launchState.launchedToTaskId;
         if (launchToTaskId != -1 &&
                 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            ssp.cancelWindowTransition(launchState.launchedToTaskId);
-            ssp.cancelThumbnailTransition(getTaskId());
+            ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
+            am.cancelWindowTransition(launchState.launchedToTaskId);
+            am.cancelThumbnailTransition(getTaskId());
         }
     }
 
@@ -755,8 +758,7 @@
         loader.deleteTaskData(event.task, false);
 
         // Remove the task from activity manager
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.removeTask(event.task.key.id);
+        ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
     }
 
     public final void onBusEvent(TaskViewDismissedEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 1533e0a..96fae35 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.view.View.MeasureSpec;
 
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
@@ -70,26 +72,28 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
 import com.android.systemui.recents.views.TaskStackView;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.DividerView;
 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * An implementation of the Recents component for the current user.  For secondary users, this can
@@ -113,10 +117,10 @@
     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
 
     /**
-     * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
+     * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
      */
-    class TaskStackListenerImpl extends TaskStackChangeListener {
+    class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
 
         @Override
         public void onTaskStackChangedBackground() {
@@ -445,7 +449,8 @@
                         growTarget);
 
                 // Only close the other system windows if we are actually showing recents
-                ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+                ActivityManagerWrapper.getInstance().closeSystemWindows(
+                        SYSTEM_DIALOG_REASON_RECENT_APPS);
                 mLastToggleTime = SystemClock.elapsedRealtime();
             }
         } catch (ActivityNotFoundException e) {
@@ -864,22 +869,22 @@
                 windowOverrideRect);
 
         RectF toTaskRect = toTransform.rect;
-        AppTransitionAnimationSpecsFuture future =
-                new RecentsTransitionHelper(mContext).getAppTransitionFuture(
-                        () -> {
-                    Rect rect = new Rect();
-                    toTaskRect.round(rect);
-                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
-                            toTransform);
-                    return Lists.newArrayList(new AppTransitionAnimationSpec(
-                            toTask.key.id, thumbnail, rect));
-                });
+        AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
+            @Override
+            public List<AppTransitionAnimationSpec> composeSpecs() {
+                Rect rect = new Rect();
+                toTaskRect.round(rect);
+                GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+                return Lists.newArrayList(new AppTransitionAnimationSpec(toTask.key.id, thumbnail,
+                        rect));
+            }
+        };
 
         // For low end ram devices, wait for transition flag is reset when Recents entrance
         // animation is complete instead of when the transition animation starts
-        return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
-                false /* scaleUp */), future);
+        return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
+                false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
+                future);
     }
 
     /**
@@ -919,7 +924,7 @@
                 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
                 mHeaderBar.onTaskViewSizeChanged(width, height);
                 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-                    return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+                    return RecentsTransition.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
                             null, 1f, 0xFFff0000);
                 } else {
                     // Workaround for b/27815919, reset the callback so that we do not trigger an
@@ -932,7 +937,7 @@
                             disabledInSafeMode);
                     mHeaderBar.onTaskDataLoaded();
                     mHeaderBar.setDimAlpha(toTransform.dimAlpha);
-                    return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+                    return RecentsTransition.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
                             mHeaderBar, 1f, 0);
                 }
             }
@@ -1047,7 +1052,7 @@
             Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
             EventBus.getDefault().send(new RecentsActivityStartingEvent());
             if (future != null) {
-                future.precacheSpecs();
+                future.composeSpecsSynchronous();
             }
         });
         EventBus.getDefault().send(hideMenuEvent);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
new file mode 100644
index 0000000..5d7f1ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
@@ -0,0 +1,35 @@
+/*
+ * 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.recents.misc;
+
+import android.content.Context;
+
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+/**
+ * An implementation of {@link TaskStackChangeListener}.
+ */
+public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener {
+
+    /**
+     * Checks that the current user matches the user's SystemUI process.
+     */
+    protected final boolean checkCurrentUserId(Context context, boolean debug) {
+        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+        return checkCurrentUserId(currentUserId, debug);
+    }
+}
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 c4ac52b..14b91f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -79,6 +79,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImpl;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
 import java.util.List;
@@ -364,32 +365,6 @@
         return insets.right > 0;
     }
 
-    /**
-     * Cancels the current window transtion to/from Recents for the given task id.
-     */
-    public void cancelWindowTransition(int taskId) {
-        if (mIam == null) return;
-
-        try {
-            mIam.cancelTaskWindowTransition(taskId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Cancels the current thumbnail transtion to/from Recents for the given task id.
-     */
-    public void cancelThumbnailTransition(int taskId) {
-        if (mIam == null) return;
-
-        try {
-            mIam.cancelTaskThumbnailTransition(taskId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-    }
-
     /** Set the task's windowing mode. */
     public void setTaskWindowingMode(int taskId, int windowingMode) {
         if (mIam == null) return;
@@ -415,18 +390,6 @@
         });
     }
 
-    /**
-     * Sends a message to close other system windows.
-     */
-    public void sendCloseSystemWindows(String reason) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mIam.closeSystemDialogs(reason);
-            } catch (RemoteException e) {
-            }
-        });
-    }
-
     public ActivityManager.TaskDescription getTaskDescription(int taskId) {
         try {
             return mIam.getTaskDescription(taskId);
@@ -623,7 +586,7 @@
      * Registers a task stack listener with the system.
      * This should be called on the main thread.
      */
-    public void registerTaskStackListener(TaskStackChangeListener listener) {
+    public void registerTaskStackListener(SysUiTaskStackChangeListener listener) {
         if (mIam == null) return;
 
         synchronized (mTaskStackChangeListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
new file mode 100644
index 0000000..99390ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 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.recents.views;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.AppTransitionAnimationSpec;
+
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to create the transition app animation specs to/from Recents
+ */
+public class RecentsTransitionComposer {
+
+    private static final String TAG = "RecentsTransitionComposer";
+
+    private Context mContext;
+    private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+    public RecentsTransitionComposer(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Composes a single animation spec for the given {@link TaskView}
+     */
+    private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
+            TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
+        GraphicBuffer b = null;
+        if (addHeaderBitmap) {
+            b = composeHeaderBitmap(taskView, transform);
+            if (b == null) {
+                return null;
+            }
+        }
+
+        Rect taskRect = new Rect();
+        transform.rect.round(taskRect);
+        // Disable in for low ram devices because each task does in Recents does not have fullscreen
+        // height (stackView height) and when transitioning to fullscreen app, the code below would
+        // force the task thumbnail to full stackView height immediately causing the transition
+        // jarring.
+        if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
+                stackView.getStack().getStackFrontMostTask()) {
+            taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
+        }
+        return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
+    }
+
+    /**
+     * Composes the transition spec when docking a task, which includes a full task bitmap.
+     */
+    public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
+            Rect bounds) {
+        mTmpTransform.fillIn(taskView);
+        Task task = taskView.getTask();
+        GraphicBuffer buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
+        return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
+                bounds));
+    }
+
+    /**
+     * Composes the animation specs for all the tasks in the target stack.
+     */
+    public List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
+            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+        // Calculate the offscreen task rect (for tasks that are not backed by views)
+        TaskView taskView = stackView.getChildViewForTask(task);
+        TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
+        Rect offscreenTaskRect = new Rect();
+        stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
+
+        // If this is a full screen stack, the transition will be towards the single, full screen
+        // task. We only need the transition spec for this task.
+
+        // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
+        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                || activityType == ACTIVITY_TYPE_ASSISTANT
+                || windowingMode == WINDOWING_MODE_UNDEFINED) {
+            List<AppTransitionAnimationSpec> specs = new ArrayList<>();
+            if (taskView == null) {
+                specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+            } else {
+                mTmpTransform.fillIn(taskView);
+                stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
+                AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
+                        mTmpTransform, true /* addHeaderBitmap */);
+                if (spec != null) {
+                    specs.add(spec);
+                }
+            }
+            return specs;
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Composes a single animation spec for the given {@link Task}
+     */
+    private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
+            Rect taskRect) {
+        return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
+    }
+
+    public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
+        float scale = transform.scale;
+        int fromWidth = (int) (transform.rect.width() * scale);
+        int fromHeight = (int) (transform.rect.height() * scale);
+        if (fromWidth == 0 || fromHeight == 0) {
+            Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+                    " at transform: " + transform);
+
+            return RecentsTransition.drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
+        } else {
+            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+                return RecentsTransition.drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f,
+                        0xFFff0000);
+            } else {
+                return RecentsTransition.drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView,
+                        scale, 0);
+            }
+        }
+    }
+
+    private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
+            TaskViewTransform transform) {
+        float scale = transform.scale;
+        int headerWidth = (int) (transform.rect.width());
+        int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+        if (headerWidth == 0 || headerHeight == 0) {
+            return null;
+        }
+
+        if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+            return RecentsTransition.drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f,
+                    0xFFff0000);
+        } else {
+            return RecentsTransition.drawViewIntoGraphicBuffer(headerWidth, headerHeight,
+                    taskView.mHeaderView, scale, 0);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
deleted file mode 100644
index 7442904..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ /dev/null
@@ -1,458 +0,0 @@
-/*
- * Copyright (C) 2014 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.recents.views;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayListCanvas;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RenderNode;
-import android.view.ThreadedRenderer;
-import android.view.View;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class to create transitions to/from Recents
- */
-public class RecentsTransitionHelper {
-
-    private static final String TAG = "RecentsTransitionHelper";
-    private static final boolean DEBUG = false;
-
-    /**
-     * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
-     * waiting for the specs to be retrieved.
-     */
-    private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
-
-    @GuardedBy("this")
-    private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
-
-    private Context mContext;
-    private Handler mHandler;
-    private TaskViewTransform mTmpTransform = new TaskViewTransform();
-
-    private class StartScreenPinningRunnableRunnable implements Runnable {
-
-        private int taskId = -1;
-
-        @Override
-        public void run() {
-            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, taskId));
-        }
-    }
-    private StartScreenPinningRunnableRunnable mStartScreenPinningRunnable
-            = new StartScreenPinningRunnableRunnable();
-
-    public RecentsTransitionHelper(Context context) {
-        mContext = context;
-        mHandler = new Handler();
-    }
-
-    /**
-     * Launches the specified {@link Task}.
-     */
-    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
-            final TaskStackView stackView, final TaskView taskView,
-            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
-
-        final ActivityOptions.OnAnimationStartedListener animStartedListener;
-        final AppTransitionAnimationSpecsFuture transitionFuture;
-        if (taskView != null) {
-
-            // Fetch window rect here already in order not to be blocked on lock contention in WM
-            // when the future calls it.
-            final Rect windowRect = Recents.getSystemServices().getWindowRect();
-            transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
-                    task, stackView, windowingMode, activityType, windowRect));
-            animStartedListener = new OnAnimationStartedListener() {
-                private boolean mHandled;
-
-                @Override
-                public void onAnimationStarted() {
-                    if (mHandled) {
-                        return;
-                    }
-                    mHandled = true;
-
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-
-                    if (screenPinningRequested) {
-                        // Request screen pinning after the animation runs
-                        mStartScreenPinningRunnable.taskId = task.key.id;
-                        mHandler.postDelayed(mStartScreenPinningRunnable, 350);
-                    }
-
-                    if (!Recents.getConfiguration().isLowRamDevice) {
-                        // Reset the state where we are waiting for the transition to start
-                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-                    }
-                }
-            };
-        } else {
-            // This is only the case if the task is not on screen (scrolled offscreen for example)
-            transitionFuture = null;
-            animStartedListener = new OnAnimationStartedListener() {
-                private boolean mHandled;
-
-                @Override
-                public void onAnimationStarted() {
-                    if (mHandled) {
-                        return;
-                    }
-                    mHandled = true;
-
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-
-                    if (!Recents.getConfiguration().isLowRamDevice) {
-                        // Reset the state where we are waiting for the transition to start
-                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-                    }
-                }
-            };
-        }
-
-        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
-        final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                mHandler, transitionFuture != null ? transitionFuture.future : null,
-                animStartedListener, true /* scaleUp */);
-        if (taskView == null) {
-            // If there is no task view, then we do not need to worry about animating out occluding
-            // task views, and we can launch immediately
-            startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                    windowingMode, activityType);
-        } else {
-            LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
-                    screenPinningRequested);
-            EventBus.getDefault().send(launchStartedEvent);
-            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
-                    activityType);
-        }
-        Recents.getSystemServices().sendCloseSystemWindows(
-                StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
-    }
-
-    public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
-        if (listener == null) {
-            return null;
-        }
-        return new IRemoteCallback.Stub() {
-            @Override
-            public void sendResult(Bundle data) throws RemoteException {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        listener.onAnimationStarted();
-                    }
-                });
-            }
-        };
-    }
-
-    /**
-     * Starts the activity for the launch task.
-     *
-     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
-     *                 we are toggling recents and the launch-to task is now offscreen.
-     */
-    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
-            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
-            int windowingMode, int activityType) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
-                activityType,
-                succeeded -> {
-            if (succeeded) {
-                // Keep track of the index of the task launch
-                int taskIndexFromFront = 0;
-                int taskIndex = stack.indexOfStackTask(task);
-                if (taskIndex > -1) {
-                    taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
-                }
-                EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
-            } else {
-                // Dismiss the task if we fail to launch it
-                if (taskView != null) {
-                    taskView.dismissTask();
-                }
-
-                // Keep track of failed launches
-                EventBus.getDefault().send(new LaunchTaskFailedEvent());
-            }
-        });
-        if (transitionFuture != null) {
-            mHandler.post(transitionFuture::precacheSpecs);
-        }
-    }
-
-    /**
-     * Creates a future which will later be queried for animation specs for this current transition.
-     *
-     * @param composer The implementation that composes the specs on the UI thread.
-     */
-    public AppTransitionAnimationSpecsFuture getAppTransitionFuture(
-            final AnimationSpecComposer composer) {
-        synchronized (this) {
-            mAppTransitionAnimationSpecs = SPECS_WAITING;
-        }
-        IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() {
-            @Override
-            public AppTransitionAnimationSpec[] get() throws RemoteException {
-                mHandler.post(() -> {
-                    synchronized (RecentsTransitionHelper.this) {
-                        mAppTransitionAnimationSpecs = composer.composeSpecs();
-                        RecentsTransitionHelper.this.notifyAll();
-                    }
-                });
-                synchronized (RecentsTransitionHelper.this) {
-                    while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
-                        try {
-                            RecentsTransitionHelper.this.wait();
-                        } catch (InterruptedException e) {}
-                    }
-                    if (mAppTransitionAnimationSpecs == null) {
-                        return null;
-                    }
-                    AppTransitionAnimationSpec[] specs
-                            = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
-                    mAppTransitionAnimationSpecs.toArray(specs);
-                    mAppTransitionAnimationSpecs = SPECS_WAITING;
-                    return specs;
-                }
-            }
-        };
-        return new AppTransitionAnimationSpecsFuture(composer, future);
-    }
-
-    /**
-     * Composes the transition spec when docking a task, which includes a full task bitmap.
-     */
-    public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
-            Rect bounds) {
-        mTmpTransform.fillIn(taskView);
-        Task task = taskView.getTask();
-        GraphicBuffer buffer = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
-        return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
-                bounds));
-    }
-
-    /**
-     * Composes the animation specs for all the tasks in the target stack.
-     */
-    private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
-            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
-        // Calculate the offscreen task rect (for tasks that are not backed by views)
-        TaskView taskView = stackView.getChildViewForTask(task);
-        TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
-        Rect offscreenTaskRect = new Rect();
-        stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
-
-        // If this is a full screen stack, the transition will be towards the single, full screen
-        // task. We only need the transition spec for this task.
-
-        // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
-        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
-        if (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                || activityType == ACTIVITY_TYPE_ASSISTANT
-                || windowingMode == WINDOWING_MODE_UNDEFINED) {
-            List<AppTransitionAnimationSpec> specs = new ArrayList<>();
-            if (taskView == null) {
-                specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
-            } else {
-                mTmpTransform.fillIn(taskView);
-                stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
-                AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
-                        mTmpTransform, true /* addHeaderBitmap */);
-                if (spec != null) {
-                    specs.add(spec);
-                }
-            }
-            return specs;
-        }
-        return Collections.emptyList();
-    }
-
-    /**
-     * Composes a single animation spec for the given {@link Task}
-     */
-    private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
-            Rect taskRect) {
-        return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
-    }
-
-    public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
-        float scale = transform.scale;
-        int fromWidth = (int) (transform.rect.width() * scale);
-        int fromHeight = (int) (transform.rect.height() * scale);
-        if (fromWidth == 0 || fromHeight == 0) {
-            Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
-                    " at transform: " + transform);
-
-            return drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
-        } else {
-            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-                return drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f, 0xFFff0000);
-            } else {
-                return drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView, scale, 0);
-            }
-        }
-    }
-
-    private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
-            TaskViewTransform transform) {
-        float scale = transform.scale;
-        int headerWidth = (int) (transform.rect.width());
-        int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
-        if (headerWidth == 0 || headerHeight == 0) {
-            return null;
-        }
-
-        if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-            return drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f, 0xFFff0000);
-        } else {
-            return drawViewIntoGraphicBuffer(headerWidth, headerHeight, taskView.mHeaderView,
-                    scale, 0);
-        }
-    }
-
-    public static GraphicBuffer drawViewIntoGraphicBuffer(int bufferWidth, int bufferHeight,
-            View view, float scale, int eraseColor) {
-        RenderNode node = RenderNode.create("RecentsTransition", null);
-        node.setLeftTopRightBottom(0, 0, bufferWidth, bufferHeight);
-        node.setClipToBounds(false);
-        DisplayListCanvas c = node.start(bufferWidth, bufferHeight);
-        c.scale(scale, scale);
-        if (eraseColor != 0) {
-            c.drawColor(eraseColor);
-        }
-        if (view != null) {
-            view.draw(c);
-        }
-        node.end(c);
-        Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
-        return hwBitmap.createGraphicBufferHandle();
-    }
-
-    /**
-     * Composes a single animation spec for the given {@link TaskView}
-     */
-    private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
-            TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
-        GraphicBuffer b = null;
-        if (addHeaderBitmap) {
-            b = composeHeaderBitmap(taskView, transform);
-            if (b == null) {
-                return null;
-            }
-        }
-
-        Rect taskRect = new Rect();
-        transform.rect.round(taskRect);
-        // Disable in for low ram devices because each task does in Recents does not have fullscreen
-        // height (stackView height) and when transitioning to fullscreen app, the code below would
-        // force the task thumbnail to full stackView height immediately causing the transition
-        // jarring.
-        if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
-                stackView.getStack().getStackFrontMostTask()) {
-            taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
-        }
-        return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
-    }
-
-    public interface AnimationSpecComposer {
-        List<AppTransitionAnimationSpec> composeSpecs();
-    }
-
-    /**
-     * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future
-     * and the anonymous class used for composing.
-     */
-    public class AppTransitionAnimationSpecsFuture {
-
-        private final AnimationSpecComposer composer;
-        private final IAppTransitionAnimationSpecsFuture future;
-
-        private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer,
-                IAppTransitionAnimationSpecsFuture future) {
-            this.composer = composer;
-            this.future = future;
-        }
-
-        public IAppTransitionAnimationSpecsFuture getFuture() {
-            return future;
-        }
-
-        /**
-         * Manually generates and caches the spec such that they are already available when the
-         * future needs.
-         */
-        public void precacheSpecs() {
-            synchronized (RecentsTransitionHelper.this) {
-                mAppTransitionAnimationSpecs = composer.composeSpecs();
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 5f12a04..f4973d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.recents.views;
 
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -28,6 +33,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.MathUtils;
@@ -54,15 +60,22 @@
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
 import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
 import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -76,8 +89,9 @@
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -101,6 +115,7 @@
 
     private static final int BUSY_RECENTS_TASK_COUNT = 3;
 
+    private Handler mHandler;
     private TaskStackView mTaskStackView;
     private TextView mStackActionButton;
     private TextView mEmptyView;
@@ -126,7 +141,7 @@
         mMultiWindowBackgroundScrim.setAlpha(alpha);
     };
 
-    private RecentsTransitionHelper mTransitionHelper;
+    private RecentsTransitionComposer mTransitionHelper;
     @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
     private RecentsViewTouchHandler mTouchHandler;
     private final FlingAnimationUtils mFlingAnimationUtils;
@@ -148,7 +163,8 @@
         setWillNotDraw(false);
 
         SystemServicesProxy ssp = Recents.getSystemServices();
-        mTransitionHelper = new RecentsTransitionHelper(getContext());
+        mHandler = new Handler();
+        mTransitionHelper = new RecentsTransitionComposer(getContext());
         mDividerSize = ssp.getDockedDividerSize(context);
         mTouchHandler = new RecentsViewTouchHandler(this);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -518,9 +534,8 @@
     /**** EventBus Events ****/
 
     public final void onBusEvent(LaunchTaskEvent event) {
-        mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
-                event.taskView, event.screenPinningRequested, event.targetWindowingMode,
-                event.targetActivityType);
+        launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
+                event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
         if (Recents.getConfiguration().isLowRamDevice) {
             EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
         }
@@ -607,17 +622,15 @@
                 };
 
                 final Rect taskRect = getTaskRect(event.taskView);
-                AppTransitionAnimationSpecsFuture future =
-                        mTransitionHelper.getAppTransitionFuture(
-                                new AnimationSpecComposer() {
-                                    @Override
-                                    public List<AppTransitionAnimationSpec> composeSpecs() {
-                                        return mTransitionHelper.composeDockAnimationSpec(
-                                                event.taskView, taskRect);
-                                    }
-                                });
+                AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
+                        getHandler()) {
+                    @Override
+                    public List<AppTransitionAnimationSpec> composeSpecs() {
+                        return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
+                    }
+                };
                 ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
-                        mTransitionHelper.wrapStartedListener(startedListener),
+                        RecentsTransition.wrapStartedListener(getHandler(), startedListener),
                         true /* scaleUp */);
 
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
@@ -881,7 +894,8 @@
      * @return the bounds of the stack action button.
      */
     Rect getStackActionButtonBoundsFromStackLayout() {
-        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
+        Rect actionButtonRect = new Rect(
+                mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
         int left, top;
         if (Recents.getConfiguration().isLowRamDevice) {
             Rect windowRect = Recents.getSystemServices().getWindowRect();
@@ -906,6 +920,140 @@
         return mStackActionButton;
     }
 
+    /**
+     * Launches the specified {@link Task}.
+     */
+    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+            final TaskStackView stackView, final TaskView taskView,
+            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
+
+        final ActivityOptions.OnAnimationStartedListener animStartedListener;
+        final AppTransitionAnimationSpecsFuture transitionFuture;
+        if (taskView != null) {
+
+            // Fetch window rect here already in order not to be blocked on lock contention in WM
+            // when the future calls it.
+            final Rect windowRect = Recents.getSystemServices().getWindowRect();
+            transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
+                @Override
+                public List<AppTransitionAnimationSpec> composeSpecs() {
+                    return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
+                            activityType, windowRect);
+                }
+            };
+            animStartedListener = new OnAnimationStartedListener() {
+                private boolean mHandled;
+
+                @Override
+                public void onAnimationStarted() {
+                    if (mHandled) {
+                        return;
+                    }
+                    mHandled = true;
+
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                    stackView.cancelAllTaskViewAnimations();
+
+                    if (screenPinningRequested) {
+                        // Request screen pinning after the animation runs
+                        mHandler.postDelayed(() -> {
+                            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
+                                    task.key.id));
+                        }, 350);
+                    }
+
+                    if (!Recents.getConfiguration().isLowRamDevice) {
+                        // Reset the state where we are waiting for the transition to start
+                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+                    }
+                }
+            };
+        } else {
+            // This is only the case if the task is not on screen (scrolled offscreen for example)
+            transitionFuture = null;
+            animStartedListener = new OnAnimationStartedListener() {
+                private boolean mHandled;
+
+                @Override
+                public void onAnimationStarted() {
+                    if (mHandled) {
+                        return;
+                    }
+                    mHandled = true;
+
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                    stackView.cancelAllTaskViewAnimations();
+
+                    if (!Recents.getConfiguration().isLowRamDevice) {
+                        // Reset the state where we are waiting for the transition to start
+                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+                    }
+                }
+            };
+        }
+
+        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
+        final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
+                mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
+                animStartedListener);
+        if (taskView == null) {
+            // If there is no task view, then we do not need to worry about animating out occluding
+            // task views, and we can launch immediately
+            startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                    windowingMode, activityType);
+        } else {
+            LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
+                    screenPinningRequested);
+            EventBus.getDefault().send(launchStartedEvent);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+                    activityType);
+        }
+        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+    }
+
+    /**
+     * Starts the activity for the launch task.
+     *
+     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+     *                 we are toggling recents and the launch-to task is now offscreen.
+     */
+    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
+            int windowingMode, int activityType) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
+                activityType,
+                succeeded -> {
+                    if (succeeded) {
+                        // Keep track of the index of the task launch
+                        int taskIndexFromFront = 0;
+                        int taskIndex = stack.indexOfStackTask(task);
+                        if (taskIndex > -1) {
+                            taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+                        }
+                        EventBus.getDefault().send(new LaunchTaskSucceededEvent(
+                                taskIndexFromFront));
+                    } else {
+                        // Dismiss the task if we fail to launch it
+                        if (taskView != null) {
+                            taskView.dismissTask();
+                        }
+
+                        // Keep track of failed launches
+                        EventBus.getDefault().send(new LaunchTaskFailedEvent());
+                    }
+                });
+        if (transitionFuture != null) {
+            mHandler.post(transitionFuture::composeSpecsSynchronous);
+        }
+    }
+
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 7bcef57..1596d12 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -439,7 +439,7 @@
         if (mMinimizedSnapAlgorithm == null) {
             mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
                     mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
-                    mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+                    mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 0997983..b2bbe30 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,8 +31,8 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.stackdivider.events.StartedDragingEvent;
 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
 
@@ -76,7 +76,7 @@
         mContext = context;
         EventBus.getDefault().register(this);
         SystemServicesProxy.getInstance(context).registerTaskStackListener(
-                new TaskStackChangeListener() {
+                new SysUiTaskStackChangeListener() {
                     @Override
                     public void onActivityForcedResizable(String packageName, int taskId,
                             int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index fed2ebe..7022c47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -45,8 +45,8 @@
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
@@ -305,10 +305,10 @@
     }
 
     /**
-     * An implementation of TaskStackChangeListener, that listens for changes in the system task
+     * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task
      * stack and notifies the navigation bar.
      */
-    private class TaskStackListenerImpl extends TaskStackChangeListener {
+    private class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
         @Override
         public void onTaskStackChanged() {
             SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index b876286b..ba4ff58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -65,8 +65,8 @@
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -768,7 +768,7 @@
         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
     }
 
-    private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
+    private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             // Listen for changes to stacks and then check which instant apps are foreground.
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 fa2b8e8..fa34d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
@@ -5265,7 +5266,8 @@
     boolean isCameraAllowedByAdmin() {
         if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) {
             return false;
-        } else if (isKeyguardShowing() && isKeyguardSecure()) {
+        } else if (mStatusBarKeyguardViewManager == null ||
+                (isKeyguardShowing() && isKeyguardSecure())) {
             // Check if the admin has disabled the camera specifically for the keyguard
             return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId)
                     & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
@@ -7096,7 +7098,9 @@
     }
 
     public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
-        return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+        return mShowLockscreenNotifications
+                && ((mDisabled2 & DISABLE2_NOTIFICATION_SHADE) == 0)
+                && !mNotificationData.isAmbient(sbn.getKey());
     }
 
     // extended in StatusBar
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index b86fc21..d758314 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.argThat;
@@ -41,10 +40,8 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.WorkLockActivity;
-import com.android.systemui.keyguard.WorkLockActivityController;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,7 +65,7 @@
     private @Mock IActivityManager mIActivityManager;
 
     private WorkLockActivityController mController;
-    private TaskStackChangeListener mTaskStackListener;
+    private SysUiTaskStackChangeListener mTaskStackListener;
 
     @Before
     public void setUp() throws Exception {
@@ -78,8 +75,8 @@
         doReturn("com.example.test").when(mContext).getPackageName();
 
         // Construct controller. Save the TaskStackListener for injecting events.
-        final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
-                ArgumentCaptor.forClass(TaskStackChangeListener.class);
+        final ArgumentCaptor<SysUiTaskStackChangeListener> listenerCaptor =
+                ArgumentCaptor.forClass(SysUiTaskStackChangeListener.class);
         mController =
                 new WorkLockActivityController(mContext, mSystemServicesProxy, mIActivityManager);
 
@@ -97,7 +94,7 @@
 
         // The overlay should start and the task the activity started in should not be removed.
         verifyStartActivity(TASK_ID, true /*taskOverlay*/);
-        verify(mSystemServicesProxy, never()).removeTask(anyInt() /*taskId*/);
+        verify(mIActivityManager, never()).removeTask(anyInt() /*taskId*/);
     }
 
     @Test
@@ -111,7 +108,7 @@
         // The task the activity started in should be removed to prevent the locked task from
         // being shown.
         verifyStartActivity(TASK_ID, true /*taskOverlay*/);
-        verify(mSystemServicesProxy).removeTask(TASK_ID);
+        verify(mIActivityManager).removeTask(TASK_ID);
     }
 
     // End of tests, start of helpers
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 2e0f394..fad6bd1 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4754,6 +4754,16 @@
     // OS: P
     DIALOG_OEM_LOCK_INFO = 1238;
 
+    // ACTION: Settings > Wi-Fi > Click one network > Auto sign in
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_WIFI_AUTO_SIGN_IN = 1239;
+
+    // Open: Settings > System > About phone > IMEI
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_IMEI_INFO = 1240;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 236fbfd..02a62e1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -112,4 +113,12 @@
         }
         return log;
     }
+
+    public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable String text) {
+        if (text == null) {
+            pw.println("null");
+        } else {
+            pw.print(text.length()); pw.println("_chars");
+        }
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 6d3d792..dac4586 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -49,6 +49,8 @@
 
 import com.android.internal.R;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
+
 import libcore.util.Objects;
 
 import java.io.PrintWriter;
@@ -466,7 +468,8 @@
         pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
         pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
         pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
-        pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
+        pw.print(prefix); pw.print("mFilterText: ");
+        Helper.printlnRedactedText(pw, mFilterText);
         pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
         pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 8d46d1e..35f83e4 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -60,9 +60,6 @@
     // Set this to true to use debug default values.
     static final boolean DB = false;
 
-    // Set this to true to have the watchdog record kernel thread stacks when it fires
-    static final boolean RECORD_KERNEL_THREADS = true;
-
     // Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
     //         timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
     //         can trigger the watchdog.
@@ -509,11 +506,6 @@
             // The system's been hanging for a minute, another second or two won't hurt much.
             SystemClock.sleep(2000);
 
-            // Pull our own kernel thread stacks as well if we're configured for that
-            if (RECORD_KERNEL_THREADS) {
-                dumpKernelStackTraces();
-            }
-
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
             doSysRq('w');
             doSysRq('l');
@@ -591,18 +583,6 @@
         }
     }
 
-    private File dumpKernelStackTraces() {
-        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
-        if (tracesPath == null || tracesPath.length() == 0) {
-            return null;
-        }
-
-        native_dumpKernelStacks(tracesPath);
-        return new File(tracesPath);
-    }
-
-    private native void native_dumpKernelStacks(String tracesPath);
-
     public static final class OpenFdMonitor {
         /**
          * Number of FDs below the soft limit that we trigger a runtime restart at. This was
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e98bb1a..d2d9aab 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4043,10 +4043,14 @@
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
                 "updateUsageStats: comp=" + component + "res=" + resumed);
         final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED,
+            component.userId, component.realActivity.getPackageName(),
+            component.realActivity.getShortClassName(), resumed ? 1 : 0);
         if (resumed) {
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(component.realActivity, component.userId,
                         UsageEvents.Event.MOVE_TO_FOREGROUND);
+
             }
             synchronized (stats) {
                 stats.noteActivityResumedLocked(component.app.uid);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d6bd2b31..7eb922c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2476,7 +2476,10 @@
             pw.println("      -e <NAME> <VALUE>: set argument <NAME> to <VALUE>.  For test runners a");
             pw.println("          common form is [-e <testrunner_flag> <value>[,<value>...]].");
             pw.println("      -p <FILE>: write profiling data to <FILE>");
-            pw.println("      -m: Write output as protobuf (machine readable)");
+            pw.println("      -m: Write output as protobuf to stdout (machine readable)");
+            pw.println("      -f <Optional PATH/TO/FILE>: Write output as protobuf to a file (machine");
+            pw.println("          readable). If path is not specified, default directory and file name will");
+            pw.println("          be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto");
             pw.println("      -w: wait for instrumentation to finish before returning.  Required for");
             pw.println("          test runners.");
             pw.println("      --user <USER_ID> | current: Specify user instrumentation runs in;");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2f0b649..481575e 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -77,7 +77,6 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -886,6 +885,7 @@
 
         Entry ent = AttributeCache.instance().get(packageName,
                 realTheme, com.android.internal.R.styleable.Window, userId);
+
         if (ent != null) {
             fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
             hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
@@ -2119,11 +2119,6 @@
     }
 
     void setRequestedOrientation(int requestedOrientation) {
-        if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
-                && appInfo.targetSdkVersion >= O_MR1) {
-            throw new IllegalStateException("Only fullscreen activities can request orientation");
-        }
-
         final int displayId = getDisplayId();
         final Configuration displayConfig =
                 mStackSupervisor.getDisplayOverrideConfiguration(displayId);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index db12ae2..52f9702 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -23,6 +23,7 @@
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -177,9 +178,22 @@
     }
 
     public void publish() {
+        LocalServices.addService(BatteryStatsInternal.class, new LocalService());
         ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
     }
 
+    private final class LocalService extends BatteryStatsInternal {
+        @Override
+        public String[] getWifiIfaces() {
+            return mStats.getWifiIfaces().clone();
+        }
+
+        @Override
+        public String[] getMobileIfaces() {
+            return mStats.getMobileIfaces().clone();
+        }
+    }
+
     private static void awaitUninterruptibly(Future<?> future) {
         while (true) {
             try {
diff --git a/services/core/java/com/android/server/am/RunningTasks.java b/services/core/java/com/android/server/am/RunningTasks.java
index 400b03a..c860df8 100644
--- a/services/core/java/com/android/server/am/RunningTasks.java
+++ b/services/core/java/com/android/server/am/RunningTasks.java
@@ -47,8 +47,10 @@
     void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
             @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
             int callingUid, boolean allowed) {
-        // For each stack on each display, add the tasks into the sorted set and then pull the first
-        // {@param maxNum} from the set
+        // Return early if there are no tasks to fetch
+        if (maxNum <= 0) {
+            return;
+        }
 
         // Gather all of the tasks across all of the tasks, and add them to the sorted set
         mTmpSortedSet.clear();
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 6e1c21e..c4e6ff6 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -163,10 +163,6 @@
     };
 
     private SyncManager getSyncManager() {
-        if (SystemProperties.getBoolean("config.disable_network", false)) {
-            return null;
-        }
-
         synchronized(mSyncManagerLock) {
             try {
                 // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index 2493dfb..d50ffe9 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -145,7 +145,8 @@
      */
     private void updateMinInterval() {
         mEffectiveMinIntervalMs = Settings.Global.getLong(mResolver,
-                Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, DEFAULT_MIN_INTERVAL_MS);
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+                DEFAULT_MIN_INTERVAL_MS);
     }
 
     public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 4cf35bc..660659f 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -803,18 +803,6 @@
             }
         };
         mGnssMetrics = new GnssMetrics();
-
-        /*
-        * A cycle of native_init() and native_cleanup() is needed so that callbacks are registered
-        * after bootup even when location is disabled. This will allow Emergency SUPL to work even
-        * when location is disabled before device restart.
-        * */
-        boolean isInitialized = native_init();
-        if(!isInitialized) {
-            Log.d(TAG, "Failed to initialize at bootup");
-        } else {
-            native_cleanup();
-        }
     }
 
     /**
@@ -2272,6 +2260,19 @@
          * this handler.
          */
         private void handleInitialize() {
+            /*
+             * A cycle of native_init() and native_cleanup() is needed so that callbacks are
+             * registered after bootup even when location is disabled.
+             * This will allow Emergency SUPL to work even when location is disabled before device
+             * restart.
+             */
+            boolean isInitialized = native_init();
+            if(!isInitialized) {
+                Log.w(TAG, "Native initialization failed at bootup");
+            } else {
+                native_cleanup();
+            }
+
             // load default GPS configuration
             // (this configuration might change in the future based on SIM changes)
             reloadGpsProperties(mContext, mProperties);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 98b8af1..6cebdd6 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -460,7 +460,7 @@
                 mRankingHelper.readXml(parser, forRestore);
             }
             // No non-system managed services are allowed on low ram devices
-            if (!ActivityManager.isLowRamDeviceStatic()) {
+            if (canUseManagedServices()) {
                 if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
                     mListeners.readXml(parser);
                     migratedManagedServices = true;
@@ -548,12 +548,6 @@
         out.endDocument();
     }
 
-    /** Use this to check if a package can post a notification or toast. */
-    private boolean checkNotificationOp(String pkg, int uid) {
-        return mAppOps.checkOp(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
-                == AppOpsManager.MODE_ALLOWED && !isPackageSuspendedForUser(pkg, uid);
-    }
-
     private static final class ToastRecord
     {
         final int pid;
@@ -2827,7 +2821,7 @@
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!mActivityManager.isLowRamDevice()) {
+                if (canUseManagedServices()) {
                     mConditionProviders.setPackageOrComponentEnabled(
                             pkg, userId, true, granted);
 
@@ -2923,7 +2917,7 @@
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!mActivityManager.isLowRamDevice()) {
+                if (canUseManagedServices()) {
                     mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
                             userId, false, granted);
                     mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
@@ -2949,7 +2943,7 @@
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!mActivityManager.isLowRamDevice()) {
+                if (canUseManagedServices()) {
                     mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
                             userId, false, granted);
                     mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
@@ -5438,6 +5432,11 @@
         }
     }
 
+    private boolean canUseManagedServices() {
+        return !mActivityManager.isLowRamDevice()
+                || mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     private class TrimCache {
         StatusBarNotification heavy;
         StatusBarNotification sbnClone;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 86a1c03..50ac409 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -209,7 +209,7 @@
 
             // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
             // flags.
-            final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+            final int dexoptFlags = getDexFlags(pkg, compilerFilter, options);
 
             for (String dexCodeIsa : dexCodeInstructionSets) {
                 int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
@@ -349,8 +349,7 @@
                 dexUseInfo.isUsedByOtherApps());
         // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
         // Secondary dex files are currently not compiled at boot.
-        int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
-                | DEXOPT_SECONDARY_DEX;
+        int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX;
         // Check the app storage and add the appropriate flags.
         if (info.deviceProtectedDataDir != null &&
                 FileUtils.contains(info.deviceProtectedDataDir, path)) {
@@ -486,11 +485,11 @@
      * filter.
      */
     private int getDexFlags(PackageParser.Package pkg, String compilerFilter,
-            boolean bootComplete) {
-        return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete);
+            DexoptOptions options) {
+        return getDexFlags(pkg.applicationInfo, compilerFilter, options);
     }
 
-    private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) {
+    private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
         int flags = info.flags;
         boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         // Profile guide compiled oat files should not be public.
@@ -501,7 +500,8 @@
                 (isPublic ? DEXOPT_PUBLIC : 0)
                 | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                 | profileFlag
-                | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0);
+                | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
+                | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
         return adjustDexoptFlags(dexFlags);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7be0cde..7837b02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8687,6 +8687,7 @@
                 pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
                 pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
             }
+            pkg.mExtras = updatedPkg;
 
             throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
                     + scanFile + " ignored: updated version " + ps.versionCode
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 19b0d9b..cf9e6f3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -52,7 +52,16 @@
     // Load the property for the given reason and check for validity. This will throw an
     // exception in case the reason or value are invalid.
     private static String getAndCheckValidity(int reason) {
-        String sysPropValue = SystemProperties.get(getSystemPropertyName(reason));
+        String sysPropName = getSystemPropertyName(reason);
+        String sysPropValue;
+        // TODO: This is a temporary hack to keep marlin booting on aosp/master while we
+        // figure out how to deal with these system properties that currently appear on
+        // vendor.
+        if ("pm.dexopt.inactive".equals(sysPropName)) {
+            sysPropValue = "verify";
+        } else {
+            sysPropValue = SystemProperties.get(sysPropName);
+        }
         if (sysPropValue == null || sysPropValue.isEmpty() ||
                 !DexFile.isValidCompilerFilter(sysPropValue)) {
             throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 533b261..8014acf 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -383,6 +383,7 @@
                 MediaStore.AUTHORITY, userId);
         if (mediaStorePackage != null) {
             grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+            grantRuntimePermissions(mediaStorePackage, PHONE_PERMISSIONS, true, userId);
         }
 
         // Downloads provider
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 22d2bcf..41534cb 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -24,7 +24,8 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.content.IntentFilter;
+import android.net.NetworkStats;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -37,15 +38,17 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
+import android.util.StatsLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
 
 import java.util.ArrayList;
 import java.util.List;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.KernelWakelockReader;
-import com.android.internal.os.KernelWakelockStats;
-import com.android.server.SystemService;
-
 import java.util.Map;
 
 /**
@@ -65,7 +68,7 @@
     private static final Object sStatsdLock = new Object();
 
     private final PendingIntent mAnomalyAlarmIntent;
-    private final PendingIntent mPollingAlarmIntent;
+    private final PendingIntent mPullingAlarmIntent;
     private final BroadcastReceiver mAppUpdateReceiver;
     private final BroadcastReceiver mUserUpdateReceiver;
 
@@ -76,8 +79,8 @@
 
         mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
                 new Intent(mContext, AnomalyAlarmReceiver.class), 0);
-        mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
-                new Intent(mContext, PollingAlarmReceiver.class), 0);
+        mPullingAlarmIntent = PendingIntent.getBroadcast(
+            mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
         mAppUpdateReceiver = new AppUpdateReceiver();
         mUserUpdateReceiver = new BroadcastReceiver() {
             @Override
@@ -144,15 +147,16 @@
     public final static class AppUpdateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
             /**
              * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
              * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+             * If we can't find the value for EXTRA_REPLACING, we default to false.
              */
-            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
-                    intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
+                    && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                 return; // Keep only replacing or normal add and remove.
             }
+            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
                     Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
@@ -205,24 +209,25 @@
         }
     }
 
-    public final static class PollingAlarmReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Slog.d(TAG, "Time to poll something.");
-            synchronized (sStatsdLock) {
-                if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
-                    return;
-                }
-                try {
-                    // Two-way call to statsd to retain AlarmManager wakelock
-                    sStatsd.informPollAlarmFired();
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
-                }
-            }
-            // AlarmManager releases its own wakelock here.
+    public final static class PullingAlarmReceiver extends BroadcastReceiver {
+      @Override
+      public void onReceive(Context context, Intent intent) {
+        if (DEBUG)
+          Slog.d(TAG, "Time to poll something.");
+        synchronized (sStatsdLock) {
+          if (sStatsd == null) {
+            Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing");
+            return;
+          }
+          try {
+            // Two-way call to statsd to retain AlarmManager wakelock
+            sStatsd.informPollAlarmFired();
+          } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to inform statsd of pulling alarm firing", e);
+          }
         }
+        // AlarmManager releases its own wakelock here.
+      }
     }
 
     @Override // Binder call
@@ -253,32 +258,32 @@
     }
 
     @Override // Binder call
-    public void setPollingAlarms(long timestampMs, long intervalMs) {
-        enforceCallingPermission();
-        if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
-                + " every " + intervalMs + "ms");
-        final long callingToken = Binder.clearCallingIdentity();
-        try {
-            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
-            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
-            // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
-            mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
-                    mPollingAlarmIntent);
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
+    public void setPullingAlarms(long timestampMs, long intervalMs) {
+      enforceCallingPermission();
+      if (DEBUG)
+        Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms");
+      final long callingToken = Binder.clearCallingIdentity();
+      try {
+        // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+        // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+        // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+        mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, mPullingAlarmIntent);
+      } finally {
+        Binder.restoreCallingIdentity(callingToken);
+      }
     }
 
     @Override // Binder call
-    public void cancelPollingAlarms() {
-        enforceCallingPermission();
-        if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
-        final long callingToken = Binder.clearCallingIdentity();
-        try {
-            mAlarmManager.cancel(mPollingAlarmIntent);
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
+    public void cancelPullingAlarms() {
+      enforceCallingPermission();
+      if (DEBUG)
+        Slog.d(TAG, "Cancelling pulling alarm");
+      final long callingToken = Binder.clearCallingIdentity();
+      try {
+        mAlarmManager.cancel(mPullingAlarmIntent);
+      } finally {
+        Binder.restoreCallingIdentity(callingToken);
+      }
     }
 
     // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
@@ -288,35 +293,168 @@
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
+    private StatsLogEventWrapper[] addNetworkStats(int tag, NetworkStats stats, boolean withFGBG) {
+        List<StatsLogEventWrapper> ret = new ArrayList<>();
+        int size = stats.size();
+        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+        for (int j = 0; j < size; j++) {
+            stats.getValues(j, entry);
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tag, withFGBG ? 6 : 5);
+            e.writeInt(entry.uid);
+            if (withFGBG) {
+                e.writeInt(entry.set);
+            }
+            e.writeLong(entry.rxBytes);
+            e.writeLong(entry.rxPackets);
+            e.writeLong(entry.txBytes);
+            e.writeLong(entry.txPackets);
+            ret.add(e);
+        }
+        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+    }
+
+    /**
+     * Allows rollups per UID but keeping the set (foreground/background) slicing.
+     * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+     */
+    private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        entry.iface = NetworkStats.IFACE_ALL;
+        entry.tag = NetworkStats.TAG_NONE;
+        entry.metered = NetworkStats.METERED_ALL;
+        entry.roaming = NetworkStats.ROAMING_ALL;
+
+        int size = stats.size();
+        NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+        for (int i = 0; i < size; i++) {
+            stats.getValues(i, recycle);
+
+            // Skip specific tags, since already counted in TAG_NONE
+            if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+            entry.set = recycle.set; // Allows slicing by background/foreground
+            entry.uid = recycle.uid;
+            entry.rxBytes = recycle.rxBytes;
+            entry.rxPackets = recycle.rxPackets;
+            entry.txBytes = recycle.txBytes;
+            entry.txPackets = recycle.txPackets;
+            // Operations purposefully omitted since we don't use them for statsd.
+            ret.combineValues(entry);
+        }
+        return ret;
+    }
+
     @Override // Binder call
     public StatsLogEventWrapper[] pullData(int pullCode) {
         enforceCallingPermission();
-        if (DEBUG) {
+        if (DEBUG)
             Slog.d(TAG, "Pulling " + pullCode);
-        }
 
-        List<StatsLogEventWrapper> ret = new ArrayList<>();
         switch (pullCode) {
-            case PULL_CODE_KERNEL_WAKELOCKS: {
+            case StatsLog.WIFI_BYTES_TRANSFERRED: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    // TODO: Consider caching the following call to get BatteryStatsInternal.
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getWifiIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    // Combine all the metrics per Uid into one record.
+                    NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                            NetworkStats.TAG_NONE, null).groupedByUid();
+                    return addNetworkStats(pullCode, stats, false);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.MOBILE_BYTES_TRANSFERRED: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getMobileIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    // Combine all the metrics per Uid into one record.
+                    NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                        NetworkStats.TAG_NONE, null).groupedByUid();
+                    return addNetworkStats(pullCode, stats, false);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.WIFI_BYTES_TRANSFERRED_BY_FG_BG: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getWifiIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    NetworkStats stats = rollupNetworkStatsByFGBG(
+                            nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                            NetworkStats.TAG_NONE, null));
+                    return addNetworkStats(pullCode, stats, true);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.MOBILE_BYTES_TRANSFERRED_BY_FG_BG: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getMobileIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    NetworkStats stats = rollupNetworkStatsByFGBG(
+                            nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                            NetworkStats.TAG_NONE, null));
+                    return addNetworkStats(pullCode, stats, true);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.KERNEL_WAKELOCKS_REPORTED: {
                 final KernelWakelockStats wakelockStats =
                         mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+                List<StatsLogEventWrapper> ret = new ArrayList();
                 for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
                     String name = ent.getKey();
                     KernelWakelockStats.Entry kws = ent.getValue();
-                    StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+                    StatsLogEventWrapper e = new StatsLogEventWrapper(pullCode, 4);
+                    e.writeString(name);
                     e.writeInt(kws.mCount);
                     e.writeInt(kws.mVersion);
                     e.writeLong(kws.mTotalTime);
-                    e.writeString(name);
                     ret.add(e);
                 }
-                break;
+                return ret.toArray(new StatsLogEventWrapper[ret.size()]);
             }
             default:
                 Slog.w(TAG, "No such pollable data as " + pullCode);
                 return null;
         }
-        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+        return null;
     }
 
     @Override // Binder call
@@ -440,7 +578,7 @@
             mContext.unregisterReceiver(mAppUpdateReceiver);
             mContext.unregisterReceiver(mUserUpdateReceiver);
             cancelAnomalyAlarm();
-            cancelPollingAlarms();
+            cancelPullingAlarms();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e873d32..98db80e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -21,7 +21,6 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -1101,52 +1100,54 @@
                     + " from " + fromToken + " to " + this);
 
             final long origId = Binder.clearCallingIdentity();
+            try {
+                // Transfer the starting window over to the new token.
+                startingData = fromToken.startingData;
+                startingSurface = fromToken.startingSurface;
+                startingDisplayed = fromToken.startingDisplayed;
+                fromToken.startingDisplayed = false;
+                startingWindow = tStartingWindow;
+                reportedVisible = fromToken.reportedVisible;
+                fromToken.startingData = null;
+                fromToken.startingSurface = null;
+                fromToken.startingWindow = null;
+                fromToken.startingMoved = true;
+                tStartingWindow.mToken = this;
+                tStartingWindow.mAppToken = this;
 
-            // Transfer the starting window over to the new token.
-            startingData = fromToken.startingData;
-            startingSurface = fromToken.startingSurface;
-            startingDisplayed = fromToken.startingDisplayed;
-            fromToken.startingDisplayed = false;
-            startingWindow = tStartingWindow;
-            reportedVisible = fromToken.reportedVisible;
-            fromToken.startingData = null;
-            fromToken.startingSurface = null;
-            fromToken.startingWindow = null;
-            fromToken.startingMoved = true;
-            tStartingWindow.mToken = this;
-            tStartingWindow.mAppToken = this;
+                if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+                        "Removing starting " + tStartingWindow + " from " + fromToken);
+                fromToken.removeChild(tStartingWindow);
+                fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+                fromToken.mHiddenSetFromTransferredStartingWindow = false;
+                addWindow(tStartingWindow);
 
-            if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
-                    "Removing starting " + tStartingWindow + " from " + fromToken);
-            fromToken.removeChild(tStartingWindow);
-            fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
-            fromToken.mHiddenSetFromTransferredStartingWindow = false;
-            addWindow(tStartingWindow);
+                // Propagate other interesting state between the tokens. If the old token is displayed,
+                // we should immediately force the new one to be displayed. If it is animating, we need
+                // to move that animation to the new one.
+                if (fromToken.allDrawn) {
+                    allDrawn = true;
+                    deferClearAllDrawn = fromToken.deferClearAllDrawn;
+                }
+                if (fromToken.firstWindowDrawn) {
+                    firstWindowDrawn = true;
+                }
+                if (!fromToken.hidden) {
+                    hidden = false;
+                    hiddenRequested = false;
+                    mHiddenSetFromTransferredStartingWindow = true;
+                }
+                setClientHidden(fromToken.mClientHidden);
+                fromToken.mAppAnimator.transferCurrentAnimation(
+                        mAppAnimator, tStartingWindow.mWinAnimator);
 
-            // Propagate other interesting state between the tokens. If the old token is displayed,
-            // we should immediately force the new one to be displayed. If it is animating, we need
-            // to move that animation to the new one.
-            if (fromToken.allDrawn) {
-                allDrawn = true;
-                deferClearAllDrawn = fromToken.deferClearAllDrawn;
+                mService.updateFocusedWindowLocked(
+                        UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
+                getDisplayContent().setLayoutNeeded();
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            if (fromToken.firstWindowDrawn) {
-                firstWindowDrawn = true;
-            }
-            if (!fromToken.hidden) {
-                hidden = false;
-                hiddenRequested = false;
-                mHiddenSetFromTransferredStartingWindow = true;
-            }
-            setClientHidden(fromToken.mClientHidden);
-            fromToken.mAppAnimator.transferCurrentAnimation(
-                    mAppAnimator, tStartingWindow.mWinAnimator);
-
-            mService.updateFocusedWindowLocked(
-                    UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
-            getDisplayContent().setLayoutNeeded();
-            mService.mWindowPlacerLocked.performSurfacePlacement();
-            Binder.restoreCallingIdentity(origId);
             return true;
         } else if (fromToken.startingData != null) {
             // The previous app was getting ready to show a
@@ -1201,15 +1202,6 @@
      */
     @Override
     int getOrientation(int candidate) {
-        // We do not allow non-fullscreen apps to influence orientation starting in O-MR1. While we
-        // do throw an exception in {@link Activity#onCreate} and
-        // {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
-        // other calculations aren't affected.
-        if (!fillsParent() && mTargetSdk >= O_MR1) {
-            // Can't specify orientation if app doesn't fill parent.
-            return SCREEN_ORIENTATION_UNSET;
-        }
-
         if (candidate == SCREEN_ORIENTATION_BEHIND) {
             // Allow app to specify orientation regardless of its visibility state if the current
             // candidate want us to use orientation behind. I.e. the visible app on-top of this one
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 401547e..8fb2be8 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -119,7 +119,7 @@
         } catch (Exception e) {
             Slog.e(TAG_WM, "Exception creating Dim surface", e);
         } finally {
-            service.closeSurfaceTransaction();
+            service.closeSurfaceTransaction("DimLayer.constructSurface");
         }
     }
 
@@ -235,7 +235,7 @@
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure setting size", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("DimLayer.setBounds");
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4c6ab3f..2f54e0e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1083,7 +1083,7 @@
             mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
         } finally {
             if (!inTransaction) {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("setRotationUnchecked");
                 if (SHOW_LIGHT_TRANSACTIONS) {
                     Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
                 }
@@ -1452,17 +1452,17 @@
     /**
      * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
      */
-    TaskStack getSplitScreenPrimaryStackStack() {
-        TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+    TaskStack getSplitScreenPrimaryStack() {
+        TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStack();
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
     /**
-     * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+     * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
      * not visible.
      */
-    TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
-        return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+    TaskStack getSplitScreenPrimaryStackIgnoringVisibility() {
+        return mTaskStackContainers.getSplitScreenPrimaryStack();
     }
 
     TaskStack getPinnedStack() {
@@ -1877,7 +1877,7 @@
             mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
         }
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
+        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2232,7 +2232,7 @@
         if (pinnedStack != null) {
             pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
         }
-        final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+        final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
         if (splitScreenPrimaryStack != null) {
             pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
         }
@@ -3401,7 +3401,7 @@
             return mPinnedStack;
         }
 
-        TaskStack getSplitScreenPrimaryStackStack() {
+        TaskStack getSplitScreenPrimaryStack() {
             return mSplitScreenPrimaryStack;
         }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 5ce4d46..5ea0e1d 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -321,7 +321,7 @@
         if (mWindow == null) {
             return;
         }
-        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
 
         // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
         final boolean visible = stack != null;
@@ -361,7 +361,7 @@
     }
 
     void positionDockedStackedDivider(Rect frame) {
-        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack();
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
@@ -458,7 +458,7 @@
         long animDuration = 0;
         if (animate) {
             final TaskStack stack =
-                    mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                    mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
             final long transitionDuration = isAnimationMaximizing()
                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -513,7 +513,7 @@
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
         notifyDockedStackExistsChanged(
-                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
+                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
                 isHomeStackResizable());
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -531,7 +531,7 @@
         final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
                 ? mDisplayContent.getStack(targetWindowingMode)
                 : null;
-        final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
+        final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
             stack.getDimBounds(mTmpRect);
@@ -543,7 +543,7 @@
                         mDimLayer.setBounds(mTmpRect);
                         mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
                     } finally {
-                        mService.closeSurfaceTransaction();
+                        mService.closeSurfaceTransaction("setResizeDimLayer");
                     }
                 }
                 mLastDimLayerRect.set(mTmpRect);
@@ -558,7 +558,7 @@
                     mService.openSurfaceTransaction();
                     mDimLayer.hide();
                 } finally {
-                    mService.closeSurfaceTransaction();
+                    mService.closeSurfaceTransaction("setResizeDimLayer");
                 }
             }
             mLastDimLayerAlpha = 0f;
@@ -616,7 +616,7 @@
     }
 
     private void checkMinimizeChanged(boolean animate) {
-        if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
+        if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
             return;
         }
         final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -778,7 +778,7 @@
     }
 
     private boolean setMinimizedDockedStack(boolean minimized) {
-        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
     }
@@ -829,7 +829,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 860ff38..79d46ce 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -185,7 +185,7 @@
                 surfaceControl.setLayerStack(display.getLayerStack());
                 surfaceControl.show();
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("performDrag");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
                         TAG_WM, "<<< CLOSE TRANSACTION performDrag");
             }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 9a955de..861fb44 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -458,7 +458,7 @@
                     + mSurfaceControl + ": pos=(" +
                     (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("notifyMoveLw");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
                     TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a710441..bcb6e673 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -457,7 +457,7 @@
         try {
             forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("removeReplacedWindows");
             if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
         }
     }
@@ -599,7 +599,7 @@
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
         }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index c25b19c..3350fea 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -296,7 +296,7 @@
             setRotationInTransaction(originalRotation);
         } finally {
             if (!inTransaction) {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation");
             }
@@ -567,7 +567,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -607,7 +607,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -629,7 +629,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7620cb0..13435d7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -419,7 +419,7 @@
         return mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || displayContent == null
-                || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
+                || displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
     }
 
     /** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -678,7 +678,7 @@
                 mChildren.get(i).forceWindowsScaleableInTransaction(force);
             }
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("forceWindowsScaleable");
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index a11b333..12f6b5a 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -637,7 +637,7 @@
         } else {
             showDimLayer();
         }
-        mService.closeSurfaceTransaction();
+        mService.closeSurfaceTransaction("updateDimLayerVisibility");
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7cee66b..6e89e3e 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -294,7 +294,7 @@
         if (mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
-                || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
+                || mDisplayContent.getSplitScreenPrimaryStack() != null) {
             return true;
         }
         return false;
@@ -523,7 +523,7 @@
         final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
                 mService.mContext.getResources(), displayWidth, displayHeight,
                 dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
-                isMinimizedDockAndHomeStackResizable());
+                getDockSide(), isMinimizedDockAndHomeStackResizable());
         final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
 
         // Recalculate the bounds based on the position of the target.
@@ -693,7 +693,7 @@
                 "animation background stackId=" + mStackId);
 
         Rect bounds = null;
-        final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        final TaskStack dockedStack = dc.getSplitScreenPrimaryStackIgnoringVisibility();
         if (inSplitScreenPrimaryWindowingMode()
                 || (dockedStack != null && inSplitScreenSecondaryWindowingMode()
                         && !dockedStack.fillsParent())) {
@@ -773,7 +773,7 @@
         }
 
         final TaskStack dockedStack =
-                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index e409a68..1912095 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -233,7 +233,7 @@
             } catch (RuntimeException e) {
                 Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("WindowAnimator");
                 if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 391f36e..bf5f4bc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -80,7 +80,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
@@ -162,7 +161,9 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -202,7 +203,6 @@
 import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
 import android.view.InputDevice;
-import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
@@ -363,6 +363,8 @@
     private static final int TRANSITION_ANIMATION_SCALE = 1;
     private static final int ANIMATION_DURATION_SCALE = 2;
 
+    final WindowTracing mWindowTracing;
+
     final private KeyguardDisableHandler mKeyguardDisableHandler;
     boolean mKeyguardGoingAway;
     // VR Vr2d Display Id.
@@ -822,15 +824,20 @@
 
     /**
      * Closes a surface transaction.
+     * @param where debug string indicating where the transaction originated
      */
-    void closeSurfaceTransaction() {
+    void closeSurfaceTransaction(String where) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
             synchronized (mWindowMap) {
-                if (mRoot.mSurfaceTraceEnabled) {
-                    mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+                try {
+                    traceStateLocked(where);
+                } finally {
+                    if (mRoot.mSurfaceTraceEnabled) {
+                        mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+                    }
+                    SurfaceControl.closeTransaction();
                 }
-                SurfaceControl.closeTransaction();
             }
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -931,6 +938,12 @@
         }, 0);
     }
 
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver result) {
+        new WindowManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
+    }
+
     private WindowManagerService(Context context, InputManagerService inputManager,
             boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
             WindowManagerPolicy policy) {
@@ -961,6 +974,8 @@
         mPolicy = policy;
         mTaskSnapshotController = new TaskSnapshotController(this);
 
+        mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
+
         LocalServices.addService(WindowManagerPolicy.class, mPolicy);
 
         if(mInputManager != null) {
@@ -1070,7 +1085,7 @@
         try {
             createWatermarkInTransaction();
         } finally {
-            closeSurfaceTransaction();
+            closeSurfaceTransaction("createWatermarkInTransaction");
         }
 
         showEmulatorDisplayOverlayIfNeeded();
@@ -3286,7 +3301,7 @@
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
             final TaskStack stack =
-                    displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                    displayContent.getSplitScreenPrimaryStackIgnoringVisibility();
             displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
                     stack != null && stack.hasTaskForUser(newUserId));
 
@@ -3574,7 +3589,7 @@
                     mCircularDisplayMask = null;
                 }
             } finally {
-                closeSurfaceTransaction();
+                closeSurfaceTransaction("showCircularMask");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
             }
@@ -3599,7 +3614,7 @@
                 }
                 mEmulatorDisplayOverlay.setVisibility(true);
             } finally {
-                closeSurfaceTransaction();
+                closeSurfaceTransaction("showEmulatorDisplayOverlay");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
             }
@@ -6322,7 +6337,7 @@
      * @param proto     Stream to write the WindowContainer object to.
      * @param trim      If true, reduce the amount of data written.
      */
-    private void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
+    void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
         mPolicy.writeToProto(proto, POLICY);
         mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
         if (mCurrentFocus != null) {
@@ -6341,6 +6356,17 @@
         mAppTransition.writeToProto(proto, APP_TRANSITION);
     }
 
+    void traceStateLocked(String where) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
+        try {
+            mWindowTracing.traceStateLocked(where, this);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Exception while tracing state", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
     private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
             ArrayList<WindowState> windows) {
         pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
@@ -6909,7 +6935,7 @@
     public int getDockedStackSide() {
         synchronized (mWindowMap) {
             final TaskStack dockedStack = getDefaultDisplayContentLocked()
-                    .getSplitScreenPrimaryStackStackIgnoringVisibility();
+                    .getSplitScreenPrimaryStackIgnoringVisibility();
             return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
new file mode 100644
index 0000000..4b98d9d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for WindowManagerService.
+ *
+ * Use with {@code adb shell cmd window ...}.
+ */
+public class WindowManagerShellCommand extends ShellCommand {
+
+    private final WindowManagerService mService;
+
+    public WindowManagerShellCommand(WindowManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        switch (cmd) {
+            case "tracing":
+                return mService.mWindowTracing.onShellCommand(this, getNextArgRequired());
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Window Manager (window) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  tracing (start | stop)");
+        pw.println("    start or stop window tracing");
+        pw.println();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4370a76..6b1932d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1849,110 +1849,109 @@
 
         final long origId = Binder.clearCallingIdentity();
 
-        disposeInputChannel();
+        try {
+            disposeInputChannel();
 
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
-                + ": mSurfaceController=" + mWinAnimator.mSurfaceController
-                + " mAnimatingExit=" + mAnimatingExit
-                + " mRemoveOnExit=" + mRemoveOnExit
-                + " mHasSurface=" + mHasSurface
-                + " surfaceShowing=" + mWinAnimator.getShown()
-                + " isAnimationSet=" + mWinAnimator.isAnimationSet()
-                + " app-animation="
-                + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
-                + " mWillReplaceWindow=" + mWillReplaceWindow
-                + " inPendingTransaction="
-                + (mAppToken != null ? mAppToken.inPendingTransaction : false)
-                + " mDisplayFrozen=" + mService.mDisplayFrozen
-                + " callers=" + Debug.getCallers(6));
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
+                    + ": mSurfaceController=" + mWinAnimator.mSurfaceController
+                    + " mAnimatingExit=" + mAnimatingExit
+                    + " mRemoveOnExit=" + mRemoveOnExit
+                    + " mHasSurface=" + mHasSurface
+                    + " surfaceShowing=" + mWinAnimator.getShown()
+                    + " isAnimationSet=" + mWinAnimator.isAnimationSet()
+                    + " app-animation="
+                    + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+                    + " mWillReplaceWindow=" + mWillReplaceWindow
+                    + " inPendingTransaction="
+                    + (mAppToken != null ? mAppToken.inPendingTransaction : false)
+                    + " mDisplayFrozen=" + mService.mDisplayFrozen
+                    + " callers=" + Debug.getCallers(6));
 
-        // Visibility of the removed window. Will be used later to update orientation later on.
-        boolean wasVisible = false;
+            // Visibility of the removed window. Will be used later to update orientation later on.
+            boolean wasVisible = false;
 
-        final int displayId = getDisplayId();
+            final int displayId = getDisplayId();
 
-        // First, see if we need to run an animation. If we do, we have to hold off on removing the
-        // window until the animation is done. If the display is frozen, just remove immediately,
-        // since the animation wouldn't be seen.
-        if (mHasSurface && mToken.okToAnimate()) {
-            if (mWillReplaceWindow) {
-                // This window is going to be replaced. We need to keep it around until the new one
-                // gets added, then we will get rid of this one.
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "Preserving " + this + " until the new one is " + "added");
-                // TODO: We are overloading mAnimatingExit flag to prevent the window state from
-                // been removed. We probably need another flag to indicate that window removal
-                // should be deffered vs. overloading the flag that says we are playing an exit
-                // animation.
-                mAnimatingExit = true;
-                mReplacingRemoveRequested = true;
-                Binder.restoreCallingIdentity(origId);
-                return;
-            }
-
-            // If we are not currently running the exit animation, we need to see about starting one
-            wasVisible = isWinVisibleLw();
-
-            if (keepVisibleDeadWindow) {
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "Not removing " + this + " because app died while it's visible");
-
-                mAppDied = true;
-                setDisplayLayoutNeeded();
-                mService.mWindowPlacerLocked.performSurfacePlacement();
-
-                // Set up a replacement input channel since the app is now dead.
-                // We need to catch tapping on the dead window to restart the app.
-                openInputChannel(null);
-                mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
-
-                Binder.restoreCallingIdentity(origId);
-                return;
-            }
-
-            if (wasVisible) {
-                final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
-
-                // Try starting an animation.
-                if (mWinAnimator.applyAnimationLocked(transit, false)) {
+            // First, see if we need to run an animation. If we do, we have to hold off on removing the
+            // window until the animation is done. If the display is frozen, just remove immediately,
+            // since the animation wouldn't be seen.
+            if (mHasSurface && mToken.okToAnimate()) {
+                if (mWillReplaceWindow) {
+                    // This window is going to be replaced. We need to keep it around until the new one
+                    // gets added, then we will get rid of this one.
+                    if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                            "Preserving " + this + " until the new one is " + "added");
+                    // TODO: We are overloading mAnimatingExit flag to prevent the window state from
+                    // been removed. We probably need another flag to indicate that window removal
+                    // should be deffered vs. overloading the flag that says we are playing an exit
+                    // animation.
                     mAnimatingExit = true;
+                    mReplacingRemoveRequested = true;
+                    return;
                 }
-                //TODO (multidisplay): Magnification is supported only for the default display.
-                if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
-                    mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
-                }
-            }
-            final boolean isAnimating =
-                    mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
-            final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
-                    && mAppToken.isLastWindow(this);
-            // We delay the removal of a window if it has a showing surface that can be used to run
-            // exit animation and it is marked as exiting.
-            // Also, If isn't the an animating starting window that is the last window in the app.
-            // We allow the removal of the non-animating starting window now as there is no
-            // additional window or animation that will trigger its removal.
-            if (mWinAnimator.getShown() && mAnimatingExit
-                    && (!lastWindowIsStartingWindow || isAnimating)) {
-                // The exit animation is running or should run... wait for it!
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "Not removing " + this + " due to exit animation ");
-                setupWindowForRemoveOnExit();
-                if (mAppToken != null) {
-                    mAppToken.updateReportedVisibilityLocked();
-                }
-                Binder.restoreCallingIdentity(origId);
-                return;
-            }
-        }
 
-        removeImmediately();
-        // Removing a visible window will effect the computed orientation
-        // So just update orientation if needed.
-        if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
-            mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+                // If we are not currently running the exit animation, we need to see about starting one
+                wasVisible = isWinVisibleLw();
+
+                if (keepVisibleDeadWindow) {
+                    if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                            "Not removing " + this + " because app died while it's visible");
+
+                    mAppDied = true;
+                    setDisplayLayoutNeeded();
+                    mService.mWindowPlacerLocked.performSurfacePlacement();
+
+                    // Set up a replacement input channel since the app is now dead.
+                    // We need to catch tapping on the dead window to restart the app.
+                    openInputChannel(null);
+                    mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+                    return;
+                }
+
+                if (wasVisible) {
+                    final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
+
+                    // Try starting an animation.
+                    if (mWinAnimator.applyAnimationLocked(transit, false)) {
+                        mAnimatingExit = true;
+                    }
+                    //TODO (multidisplay): Magnification is supported only for the default display.
+                    if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+                        mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+                    }
+                }
+                final boolean isAnimating =
+                        mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+                final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
+                        && mAppToken.isLastWindow(this);
+                // We delay the removal of a window if it has a showing surface that can be used to run
+                // exit animation and it is marked as exiting.
+                // Also, If isn't the an animating starting window that is the last window in the app.
+                // We allow the removal of the non-animating starting window now as there is no
+                // additional window or animation that will trigger its removal.
+                if (mWinAnimator.getShown() && mAnimatingExit
+                        && (!lastWindowIsStartingWindow || isAnimating)) {
+                    // The exit animation is running or should run... wait for it!
+                    if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                            "Not removing " + this + " due to exit animation ");
+                    setupWindowForRemoveOnExit();
+                    if (mAppToken != null) {
+                        mAppToken.updateReportedVisibilityLocked();
+                    }
+                    return;
+                }
+            }
+
+            removeImmediately();
+            // Removing a visible window will effect the computed orientation
+            // So just update orientation if needed.
+            if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+            }
+            mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
-        mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
-        Binder.restoreCallingIdentity(origId);
     }
 
     private void setupWindowForRemoveOnExit() {
@@ -2361,7 +2360,7 @@
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
                             final TaskStack stack =
-                                    dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                                    dc.getSplitScreenPrimaryStackIgnoringVisibility();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5266903..86397ae 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -732,7 +732,7 @@
             mSurfaceController.setLayerStackInTransaction(getLayerStack());
             mSurfaceController.setLayer(mAnimLayer);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("createSurfaceLocked");
         }
 
         mLastHidden = true;
@@ -1711,7 +1711,7 @@
             Slog.w(TAG, "Error positioning surface of " + mWin
                     + " pos=(" + left + "," + top + ")", e);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setWallpaperOffset");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setWallpaperOffset");
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index edd650a..a214523 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -259,7 +259,7 @@
                     mSurfaceControl.setLayer(layer);
                 }
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("setLayer");
             }
         }
     }
@@ -385,7 +385,7 @@
         try {
             mSurfaceControl.setTransparentRegionHint(region);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setTransparentRegion");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setTransparentRegion");
         }
@@ -403,7 +403,7 @@
         try {
             mSurfaceControl.setOpaque(isOpaque);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setOpaqueLocked");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
         }
     }
@@ -420,7 +420,7 @@
         try {
             mSurfaceControl.setSecure(isSecure);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setSecure");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d57fdd2..cd5e475 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -429,7 +429,7 @@
             try {
                 mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
new file mode 100644
index 0000000..5657f6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.proto.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WHERE;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+
+import android.content.Context;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * time series of window manager state can be analyzed after the fact.
+ */
+class WindowTracing {
+
+    private static final String TAG = "WindowTracing";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+    private final Object mLock = new Object();
+    private final File mTraceFile;
+    private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+
+    private boolean mEnabled;
+    private volatile boolean mEnabledLockFree;
+
+    WindowTracing(File file) {
+        mTraceFile = file;
+    }
+
+    void startTrace(PrintWriter pw) throws IOException {
+        synchronized (mLock) {
+            logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+            mWriteQueue.clear();
+            mTraceFile.delete();
+            try (OutputStream os = new FileOutputStream(mTraceFile)) {
+                ProtoOutputStream proto = new ProtoOutputStream(os);
+                proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+                proto.flush();
+            }
+            mEnabled = mEnabledLockFree = true;
+        }
+    }
+
+    private void logAndPrintln(PrintWriter pw, String msg) {
+        Log.i(TAG, msg);
+        pw.println(msg);
+        pw.flush();
+    }
+
+    void stopTrace(PrintWriter pw) {
+        synchronized (mLock) {
+            logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+            mEnabled = mEnabledLockFree = false;
+            while (!mWriteQueue.isEmpty()) {
+                if (mEnabled) {
+                    logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+                    throw new IllegalStateException("tracing enabled while waiting for flush.");
+                }
+                try {
+                    mLock.wait();
+                    mLock.notify();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+            logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+        }
+    }
+
+    void appendTraceEntry(ProtoOutputStream proto) {
+        if (!mEnabledLockFree) {
+            return;
+        }
+
+        if (!mWriteQueue.offer(proto)) {
+            Log.e(TAG, "Dropping window trace entry, queue full");
+        }
+    }
+
+    void loop() {
+        for (;;) {
+            loopOnce();
+        }
+    }
+
+    @VisibleForTesting
+    void loopOnce() {
+        ProtoOutputStream proto;
+        try {
+            proto = mWriteQueue.take();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return;
+        }
+
+        synchronized (mLock) {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+                try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
+                    os.write(proto.getBytes());
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to write file " + mTraceFile, e);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+            }
+            mLock.notify();
+        }
+    }
+
+    boolean isEnabled() {
+        return mEnabledLockFree;
+    }
+
+    static WindowTracing createDefaultAndStartLooper(Context context) {
+        File file = new File("/data/system/window_trace.proto");
+        WindowTracing windowTracing = new WindowTracing(file);
+        new Thread(windowTracing::loop, "window_tracing").start();
+        return windowTracing;
+    }
+
+    int onShellCommand(ShellCommand shell, String cmd) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        try {
+            switch (cmd) {
+                case "start":
+                    startTrace(pw);
+                    return 0;
+                case "stop":
+                    stopTrace(pw);
+                    return 0;
+                default:
+                    pw.println("Unknown command: " + cmd);
+                    return -1;
+            }
+        } catch (IOException e) {
+            logAndPrintln(pw, e.toString());
+            throw new RuntimeException(e);
+        }
+    }
+
+    void traceStateLocked(String where, WindowManagerService service) {
+        if (!isEnabled()) {
+            return;
+        }
+        ProtoOutputStream os = new ProtoOutputStream();
+        long tokenOuter = os.start(ENTRY);
+        os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+        os.write(WHERE, where);
+
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
+        try {
+            long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+            service.writeToProtoLocked(os, true /* trim */);
+            os.end(tokenInner);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+        os.end(tokenOuter);
+        appendTraceEntry(os);
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+    }
+}
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 3901ceb..d4ffa70 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -48,8 +48,6 @@
 
     status_t err;
 
-    configureRpcThreadpool(5, false /* callerWillJoin */);
-
     JavaVM *vm;
     LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index e8ef168..2f45181 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -92,7 +92,6 @@
     register_android_server_tv_TvUinputBridge(env);
     register_android_server_tv_TvInputHal(env);
     register_android_server_PersistentDataBlockService(env);
-    register_android_server_Watchdog(env);
     register_android_server_HardwarePropertiesManagerService(env);
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index de5d879..a2c2aeb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -545,11 +545,9 @@
         traceEnd();
 
         // Bring up recovery system in case a rescue party needs a reboot
-        if (!SystemProperties.getBoolean("config.disable_noncore", false)) {
-            traceBeginAndSlog("StartRecoverySystemService");
-            mSystemServiceManager.startService(RecoverySystemService.class);
-            traceEnd();
-        }
+        traceBeginAndSlog("StartRecoverySystemService");
+        mSystemServiceManager.startService(RecoverySystemService.class);
+        traceEnd();
 
         // Now that we have the bare essentials of the OS up and running, take
         // note that we just booted, which might send out a rescue party if
@@ -660,6 +658,9 @@
         mSystemServiceManager.startService(DropBoxManagerService.class);
         traceEnd();
 
+        // First hwbinder call is in BatteryService.
+        android.os.HwBinder.startRpcThreadPool(5, false /* callerWillJoin */);
+
         traceBeginAndSlog("StartBatteryService");
         // Tracks the battery level.  Requires LightService.
         mSystemServiceManager.startService(BatteryService.class);
@@ -705,13 +706,7 @@
         MmsServiceBroker mmsService = null;
         HardwarePropertiesManagerService hardwarePropertiesService = null;
 
-        boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
-        boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
-        boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
         boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
-        boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
-        boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false);
-        boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime", false);
         boolean disableRtt = SystemProperties.getBoolean("config.disable_rtt", false);
         boolean disableMediaProjection = SystemProperties.getBoolean("config.disable_mediaproj",
                 false);
@@ -875,8 +870,6 @@
             } else if (!context.getPackageManager().hasSystemFeature
                        (PackageManager.FEATURE_BLUETOOTH)) {
                 Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
-            } else if (disableBluetooth) {
-                Slog.i(TAG, "Bluetooth Service disabled by config");
             } else {
                 traceBeginAndSlog("StartBluetoothService");
                 mSystemServiceManager.startService(BluetoothService.class);
@@ -927,8 +920,7 @@
         traceEnd();
 
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-            if (!disableStorage &&
-                    !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+            if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
                 traceBeginAndSlog("StartStorageManagerService");
                 try {
                     /*
@@ -978,42 +970,40 @@
         traceEnd();
 
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartLockSettingsService");
-                try {
-                    mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
-                    lockSettings = ILockSettings.Stub.asInterface(
-                            ServiceManager.getService("lock_settings"));
-                } catch (Throwable e) {
-                    reportWtf("starting LockSettingsService service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartLockSettingsService");
+            try {
+                mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+                lockSettings = ILockSettings.Stub.asInterface(
+                    ServiceManager.getService("lock_settings"));
+            } catch (Throwable e) {
+                reportWtf("starting LockSettingsService service", e);
+            }
+            traceEnd();
 
-                final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
-                if (hasPdb) {
-                    traceBeginAndSlog("StartPersistentDataBlock");
-                    mSystemServiceManager.startService(PersistentDataBlockService.class);
-                    traceEnd();
-                }
-
-                if (hasPdb || OemLockService.isHalPresent()) {
-                    // Implementation depends on pdb or the OemLock HAL
-                    traceBeginAndSlog("StartOemLockService");
-                    mSystemServiceManager.startService(OemLockService.class);
-                    traceEnd();
-                }
-
-                traceBeginAndSlog("StartDeviceIdleController");
-                mSystemServiceManager.startService(DeviceIdleController.class);
-                traceEnd();
-
-                // Always start the Device Policy Manager, so that the API is compatible with
-                // API8.
-                traceBeginAndSlog("StartDevicePolicyManager");
-                mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+            final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
+            if (hasPdb) {
+                traceBeginAndSlog("StartPersistentDataBlock");
+                mSystemServiceManager.startService(PersistentDataBlockService.class);
                 traceEnd();
             }
 
+            if (hasPdb || OemLockService.isHalPresent()) {
+                // Implementation depends on pdb or the OemLock HAL
+                traceBeginAndSlog("StartOemLockService");
+                mSystemServiceManager.startService(OemLockService.class);
+                traceEnd();
+            }
+
+            traceBeginAndSlog("StartDeviceIdleController");
+            mSystemServiceManager.startService(DeviceIdleController.class);
+            traceEnd();
+
+            // Always start the Device Policy Manager, so that the API is compatible with
+            // API8.
+            traceBeginAndSlog("StartDevicePolicyManager");
+            mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+            traceEnd();
+
             if (!disableSystemUI) {
                 traceBeginAndSlog("StartStatusBarManagerService");
                 try {
@@ -1025,153 +1015,145 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartClipboardService");
-                mSystemServiceManager.startService(ClipboardService.class);
-                traceEnd();
+            traceBeginAndSlog("StartClipboardService");
+            mSystemServiceManager.startService(ClipboardService.class);
+            traceEnd();
+
+            traceBeginAndSlog("StartNetworkManagementService");
+            try {
+                networkManagement = NetworkManagementService.create(context);
+                ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkManagement Service", e);
             }
+            traceEnd();
 
-            if (!disableNetwork) {
-                traceBeginAndSlog("StartNetworkManagementService");
-                try {
-                    networkManagement = NetworkManagementService.create(context);
-                    ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkManagement Service", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartIpSecService");
-                try {
-                    ipSecService = IpSecService.create(context);
-                    ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
-                } catch (Throwable e) {
-                    reportWtf("starting IpSec Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartIpSecService");
+            try {
+                ipSecService = IpSecService.create(context);
+                ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+            } catch (Throwable e) {
+                reportWtf("starting IpSec Service", e);
             }
+            traceEnd();
 
-            if (!disableNonCoreServices && !disableTextServices) {
+            if (!disableTextServices) {
                 traceBeginAndSlog("StartTextServicesManager");
                 mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
                 traceEnd();
             }
 
-            if (!disableNetwork) {
-                traceBeginAndSlog("StartNetworkScoreService");
-                try {
-                    networkScore = new NetworkScoreService(context);
-                    ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
-                } catch (Throwable e) {
-                    reportWtf("starting Network Score Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkScoreService");
+            try {
+                networkScore = new NetworkScoreService(context);
+                ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
+            } catch (Throwable e) {
+                reportWtf("starting Network Score Service", e);
+            }
+            traceEnd();
 
-                traceBeginAndSlog("StartNetworkStatsService");
-                try {
-                    networkStats = NetworkStatsService.create(context, networkManagement);
-                    ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkStats Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkStatsService");
+            try {
+                networkStats = NetworkStatsService.create(context, networkManagement);
+                ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkStats Service", e);
+            }
+            traceEnd();
 
-                traceBeginAndSlog("StartNetworkPolicyManagerService");
-                try {
-                    networkPolicy = new NetworkPolicyManagerService(context,
-                            mActivityManagerService, networkStats, networkManagement);
-                    ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkPolicy Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkPolicyManagerService");
+            try {
+                networkPolicy = new NetworkPolicyManagerService(context,
+                    mActivityManagerService, networkStats, networkManagement);
+                ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkPolicy Service", e);
+            }
+            traceEnd();
 
-                // Wifi Service must be started first for wifi-related services.
-                traceBeginAndSlog("StartWifi");
-                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
-                traceEnd();
-                traceBeginAndSlog("StartWifiScanning");
-                mSystemServiceManager.startService(
-                        "com.android.server.wifi.scanner.WifiScanningService");
-                traceEnd();
+            // Wifi Service must be started first for wifi-related services.
+            traceBeginAndSlog("StartWifi");
+            mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+            traceEnd();
+            traceBeginAndSlog("StartWifiScanning");
+            mSystemServiceManager.startService(
+                "com.android.server.wifi.scanner.WifiScanningService");
+            traceEnd();
 
-                if (!disableRtt) {
-                    traceBeginAndSlog("StartWifiRtt");
-                    mSystemServiceManager.startService("com.android.server.wifi.RttService");
-                    traceEnd();
-
-                    if (context.getPackageManager().hasSystemFeature(
-                            PackageManager.FEATURE_WIFI_RTT)) {
-                        traceBeginAndSlog("StartRttService");
-                        mSystemServiceManager.startService(
-                                "com.android.server.wifi.rtt.RttService");
-                        traceEnd();
-                    }
-                }
+            if (!disableRtt) {
+                traceBeginAndSlog("StartWifiRtt");
+                mSystemServiceManager.startService("com.android.server.wifi.RttService");
+                traceEnd();
 
                 if (context.getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_WIFI_AWARE)) {
-                    traceBeginAndSlog("StartWifiAware");
-                    mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+                    PackageManager.FEATURE_WIFI_RTT)) {
+                    traceBeginAndSlog("StartRttService");
+                    mSystemServiceManager.startService(
+                        "com.android.server.wifi.rtt.RttService");
                     traceEnd();
                 }
+            }
 
-                if (context.getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_WIFI_DIRECT)) {
-                    traceBeginAndSlog("StartWifiP2P");
-                    mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_AWARE)) {
+                traceBeginAndSlog("StartWifiAware");
+                mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (context.getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_LOWPAN)) {
-                    traceBeginAndSlog("StartLowpan");
-                    mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_DIRECT)) {
+                traceBeginAndSlog("StartWifiP2P");
+                mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
-                    mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
-                    traceBeginAndSlog("StartEthernet");
-                    mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LOWPAN)) {
+                traceBeginAndSlog("StartLowpan");
+                mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                traceBeginAndSlog("StartConnectivityService");
-                try {
-                    connectivity = new ConnectivityService(
-                            context, networkManagement, networkStats, networkPolicy);
-                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+                traceBeginAndSlog("StartEthernet");
+                mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
+                traceEnd();
+            }
+
+            traceBeginAndSlog("StartConnectivityService");
+            try {
+                connectivity = new ConnectivityService(
+                    context, networkManagement, networkStats, networkPolicy);
+                ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
                             /* allowIsolated= */ false,
-                            DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
-                    networkStats.bindConnectivityManager(connectivity);
-                    networkPolicy.bindConnectivityManager(connectivity);
-                } catch (Throwable e) {
-                    reportWtf("starting Connectivity Service", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartNsdService");
-                try {
-                    serviceDiscovery = NsdService.create(context);
-                    ServiceManager.addService(
-                            Context.NSD_SERVICE, serviceDiscovery);
-                } catch (Throwable e) {
-                    reportWtf("starting Service Discovery Service", e);
-                }
-                traceEnd();
+                    DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+                networkStats.bindConnectivityManager(connectivity);
+                networkPolicy.bindConnectivityManager(connectivity);
+            } catch (Throwable e) {
+                reportWtf("starting Connectivity Service", e);
             }
+            traceEnd();
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartUpdateLockService");
-                try {
-                    ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
-                            new UpdateLockService(context));
-                } catch (Throwable e) {
-                    reportWtf("starting UpdateLockService", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNsdService");
+            try {
+                serviceDiscovery = NsdService.create(context);
+                ServiceManager.addService(
+                    Context.NSD_SERVICE, serviceDiscovery);
+            } catch (Throwable e) {
+                reportWtf("starting Service Discovery Service", e);
             }
+            traceEnd();
+
+            traceBeginAndSlog("StartUpdateLockService");
+            try {
+                ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
+                    new UpdateLockService(context));
+            } catch (Throwable e) {
+                reportWtf("starting UpdateLockService", e);
+            }
+            traceEnd();
 
             traceBeginAndSlog("StartNotificationManager");
             mSystemServiceManager.startService(NotificationManagerService.class);
@@ -1185,27 +1167,25 @@
             mSystemServiceManager.startService(DeviceStorageMonitorService.class);
             traceEnd();
 
-            if (!disableLocation) {
-                traceBeginAndSlog("StartLocationManagerService");
-                try {
-                    location = new LocationManagerService(context);
-                    ServiceManager.addService(Context.LOCATION_SERVICE, location);
-                } catch (Throwable e) {
-                    reportWtf("starting Location Manager", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartCountryDetectorService");
-                try {
-                    countryDetector = new CountryDetectorService(context);
-                    ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
-                } catch (Throwable e) {
-                    reportWtf("starting Country Detector", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartLocationManagerService");
+            try {
+                location = new LocationManagerService(context);
+                ServiceManager.addService(Context.LOCATION_SERVICE, location);
+            } catch (Throwable e) {
+                reportWtf("starting Location Manager", e);
             }
+            traceEnd();
 
-            if (!disableNonCoreServices && !disableSearchManager) {
+            traceBeginAndSlog("StartCountryDetectorService");
+            try {
+                countryDetector = new CountryDetectorService(context);
+                ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+            } catch (Throwable e) {
+                reportWtf("starting Country Detector", e);
+            }
+            traceEnd();
+
+            if (!disableSearchManager) {
                 traceBeginAndSlog("StartSearchManagerService");
                 try {
                     mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
@@ -1215,8 +1195,7 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices && context.getResources().getBoolean(
-                        R.bool.config_enableWallpaperService)) {
+            if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
                 traceBeginAndSlog("StartWallpaperManagerService");
                 mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
                 traceEnd();
@@ -1232,16 +1211,14 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartDockObserver");
-                mSystemServiceManager.startService(DockObserver.class);
-                traceEnd();
+            traceBeginAndSlog("StartDockObserver");
+            mSystemServiceManager.startService(DockObserver.class);
+            traceEnd();
 
-                if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-                    traceBeginAndSlog("StartThermalObserver");
-                    mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                traceBeginAndSlog("StartThermalObserver");
+                mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
+                traceEnd();
             }
 
             traceBeginAndSlog("StartWiredAccessoryManager");
@@ -1254,46 +1231,44 @@
             }
             traceEnd();
 
-            if (!disableNonCoreServices) {
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
-                    // Start MIDI Manager service
-                    traceBeginAndSlog("StartMidiManager");
-                    mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+                // Start MIDI Manager service
+                traceBeginAndSlog("StartMidiManager");
+                mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
-                        || mPackageManager.hasSystemFeature(
-                                PackageManager.FEATURE_USB_ACCESSORY)) {
-                    // Manage USB host and device support
-                    traceBeginAndSlog("StartUsbService");
-                    mSystemServiceManager.startService(USB_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
+                || mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_USB_ACCESSORY)) {
+                // Manage USB host and device support
+                traceBeginAndSlog("StartUsbService");
+                mSystemServiceManager.startService(USB_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (!disableSerial) {
-                    traceBeginAndSlog("StartSerialService");
-                    try {
-                        // Serial port support
-                        serial = new SerialService(context);
-                        ServiceManager.addService(Context.SERIAL_SERVICE, serial);
-                    } catch (Throwable e) {
-                        Slog.e(TAG, "Failure starting SerialService", e);
-                    }
-                    traceEnd();
-                }
-
-                traceBeginAndSlog("StartHardwarePropertiesManagerService");
+            if (!disableSerial) {
+                traceBeginAndSlog("StartSerialService");
                 try {
-                    hardwarePropertiesService = new HardwarePropertiesManagerService(context);
-                    ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
-                            hardwarePropertiesService);
+                    // Serial port support
+                    serial = new SerialService(context);
+                    ServiceManager.addService(Context.SERIAL_SERVICE, serial);
                 } catch (Throwable e) {
-                    Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+                    Slog.e(TAG, "Failure starting SerialService", e);
                 }
                 traceEnd();
             }
 
+            traceBeginAndSlog("StartHardwarePropertiesManagerService");
+            try {
+                hardwarePropertiesService = new HardwarePropertiesManagerService(context);
+                ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
+                    hardwarePropertiesService);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+            }
+            traceEnd();
+
             traceBeginAndSlog("StartTwilightService");
             mSystemServiceManager.startService(TwilightService.class);
             traceEnd();
@@ -1312,48 +1287,46 @@
             mSystemServiceManager.startService(SoundTriggerService.class);
             traceEnd();
 
-            if (!disableNonCoreServices) {
-                if (!disableTrustManager) {
-                    traceBeginAndSlog("StartTrustManager");
-                    mSystemServiceManager.startService(TrustManagerService.class);
-                    traceEnd();
-                }
-
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
-                    traceBeginAndSlog("StartBackupManager");
-                    mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
-                    traceEnd();
-                }
-
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
-                    || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
-                    traceBeginAndSlog("StartAppWidgerService");
-                    mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
-                    traceEnd();
-                }
-
-                // We need to always start this service, regardless of whether the
-                // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
-                // of initializing various settings.  It will internally modify its behavior
-                // based on that feature.
-                traceBeginAndSlog("StartVoiceRecognitionManager");
-                mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
-                traceEnd();
-
-                if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
-                    traceBeginAndSlog("StartGestureLauncher");
-                    mSystemServiceManager.startService(GestureLauncherService.class);
-                    traceEnd();
-                }
-                traceBeginAndSlog("StartSensorNotification");
-                mSystemServiceManager.startService(SensorNotificationService.class);
-                traceEnd();
-
-                traceBeginAndSlog("StartContextHubSystemService");
-                mSystemServiceManager.startService(ContextHubSystemService.class);
+            if (!disableTrustManager) {
+                traceBeginAndSlog("StartTrustManager");
+                mSystemServiceManager.startService(TrustManagerService.class);
                 traceEnd();
             }
 
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
+                traceBeginAndSlog("StartBackupManager");
+                mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+                traceEnd();
+            }
+
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
+                || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
+                traceBeginAndSlog("StartAppWidgerService");
+                mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+                traceEnd();
+            }
+
+            // We need to always start this service, regardless of whether the
+            // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
+            // of initializing various settings.  It will internally modify its behavior
+            // based on that feature.
+            traceBeginAndSlog("StartVoiceRecognitionManager");
+            mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+            traceEnd();
+
+            if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
+                traceBeginAndSlog("StartGestureLauncher");
+                mSystemServiceManager.startService(GestureLauncherService.class);
+                traceEnd();
+            }
+            traceBeginAndSlog("StartSensorNotification");
+            mSystemServiceManager.startService(SensorNotificationService.class);
+            traceEnd();
+
+            traceBeginAndSlog("StartContextHubSystemService");
+            mSystemServiceManager.startService(ContextHubSystemService.class);
+            traceEnd();
+
             traceBeginAndSlog("StartDiskStatsService");
             try {
                 ServiceManager.addService("diskstats", new DiskStatsService(context));
@@ -1375,16 +1348,14 @@
                 traceEnd();
             }
 
-            if (!disableNetwork && !disableNetworkTime) {
-                traceBeginAndSlog("StartNetworkTimeUpdateService");
-                try {
-                    networkTimeUpdater = new NetworkTimeUpdateService(context);
-                    ServiceManager.addService("network_time_update_service", networkTimeUpdater);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkTimeUpdate service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkTimeUpdateService");
+            try {
+                networkTimeUpdater = new NetworkTimeUpdateService(context);
+                ServiceManager.addService("network_time_update_service", networkTimeUpdater);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkTimeUpdate service", e);
             }
+            traceEnd();
 
             traceBeginAndSlog("StartCommonTimeManagementService");
             try {
@@ -1395,38 +1366,32 @@
             }
             traceEnd();
 
-            if (!disableNetwork) {
-                traceBeginAndSlog("CertBlacklister");
-                try {
-                    CertBlacklister blacklister = new CertBlacklister(context);
-                } catch (Throwable e) {
-                    reportWtf("starting CertBlacklister", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("CertBlacklister");
+            try {
+                CertBlacklister blacklister = new CertBlacklister(context);
+            } catch (Throwable e) {
+                reportWtf("starting CertBlacklister", e);
             }
+            traceEnd();
 
-            if (!disableNetwork && !disableNonCoreServices && EmergencyAffordanceManager.ENABLED) {
+            if (EmergencyAffordanceManager.ENABLED) {
                 // EmergencyMode service
                 traceBeginAndSlog("StartEmergencyAffordanceService");
                 mSystemServiceManager.startService(EmergencyAffordanceService.class);
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
-                traceBeginAndSlog("StartDreamManager");
-                mSystemServiceManager.startService(DreamManagerService.class);
-                traceEnd();
-            }
+            // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
+            traceBeginAndSlog("StartDreamManager");
+            mSystemServiceManager.startService(DreamManagerService.class);
+            traceEnd();
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("AddGraphicsStatsService");
-                ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
-                        new GraphicsStatsService(context));
-                traceEnd();
-            }
+            traceBeginAndSlog("AddGraphicsStatsService");
+            ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+                new GraphicsStatsService(context));
+            traceEnd();
 
-            if (!disableNonCoreServices && CoverageService.ENABLED) {
+            if (CoverageService.ENABLED) {
                 traceBeginAndSlog("AddCoverageService");
                 ServiceManager.addService(CoverageService.COVERAGE_SERVICE, new CoverageService());
                 traceEnd();
@@ -1477,38 +1442,37 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartMediaRouterService");
-                try {
-                    mediaRouter = new MediaRouterService(context);
-                    ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
-                } catch (Throwable e) {
-                    reportWtf("starting MediaRouterService", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartMediaRouterService");
+            try {
+                mediaRouter = new MediaRouterService(context);
+                ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+            } catch (Throwable e) {
+                reportWtf("starting MediaRouterService", e);
+            }
+            traceEnd();
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-                    traceBeginAndSlog("StartFingerprintSensor");
-                    mSystemServiceManager.startService(FingerprintService.class);
-                    traceEnd();
-                }
-
-                traceBeginAndSlog("StartBackgroundDexOptService");
-                try {
-                    BackgroundDexOptService.schedule(context);
-                } catch (Throwable e) {
-                    reportWtf("starting StartBackgroundDexOptService", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartPruneInstantAppsJobService");
-                try {
-                    PruneInstantAppsJobService.schedule(context);
-                } catch (Throwable e) {
-                    reportWtf("StartPruneInstantAppsJobService", e);
-                }
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+                traceBeginAndSlog("StartFingerprintSensor");
+                mSystemServiceManager.startService(FingerprintService.class);
                 traceEnd();
             }
+
+            traceBeginAndSlog("StartBackgroundDexOptService");
+            try {
+                BackgroundDexOptService.schedule(context);
+            } catch (Throwable e) {
+                reportWtf("starting StartBackgroundDexOptService", e);
+            }
+            traceEnd();
+
+            traceBeginAndSlog("StartPruneInstantAppsJobService");
+            try {
+                PruneInstantAppsJobService.schedule(context);
+            } catch (Throwable e) {
+                reportWtf("StartPruneInstantAppsJobService", e);
+            }
+            traceEnd();
+
             // LauncherAppsService uses ShortcutService.
             traceBeginAndSlog("StartShortcutServiceLifecycle");
             mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
@@ -1519,7 +1483,7 @@
             traceEnd();
         }
 
-        if (!disableNonCoreServices && !disableMediaProjection) {
+        if (!disableMediaProjection) {
             traceBeginAndSlog("StartMediaProjectionManager");
             mSystemServiceManager.startService(MediaProjectionManagerService.class);
             traceEnd();
@@ -1530,17 +1494,15 @@
             mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
             traceEnd();
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartWearTimeService");
-                mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
-                mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
-                traceEnd();
+            traceBeginAndSlog("StartWearTimeService");
+            mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
+            mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
+            traceEnd();
 
-                if (enableLeftyService) {
-                    traceBeginAndSlog("StartWearLeftyService");
-                    mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (enableLeftyService) {
+                traceBeginAndSlog("StartWearLeftyService");
+                mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
+                traceEnd();
             }
         }
 
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2452b44..70e8a16 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -20,6 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 
 import static junit.framework.Assert.assertEquals;
@@ -187,6 +188,7 @@
         final LightsManager mockLightsManager = mock(LightsManager.class);
         when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
 
         // write to a test file; the system file isn't readable from tests
         mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -1472,9 +1474,9 @@
         mBinderService.setNotificationListenerAccessGranted(c, true);
 
         verify(mListeners, never()).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true);
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mConditionProviders, never()).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true);
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean());
     }
@@ -1485,11 +1487,10 @@
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         mBinderService.setNotificationAssistantAccessGranted(c, true);
 
-
         verify(mListeners, never()).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true);
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mConditionProviders, never()).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, false, true);
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean());
     }
@@ -1501,14 +1502,77 @@
         mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
 
         verify(mListeners, never()).setPackageOrComponentEnabled(
-                c.flattenToString(), 0, true, true);
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mConditionProviders, never()).setPackageOrComponentEnabled(
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mAssistants, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testSetListenerAccess_doesNothingOnLowRam_exceptWatch() throws Exception {
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
+        when(mActivityManager.isLowRamDevice()).thenReturn(true);
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationListenerAccessGranted(c, true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mListeners, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, true, true);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
                 c.flattenToString(), 0, false, true);
         verify(mAssistants, never()).setPackageOrComponentEnabled(
                 any(), anyInt(), anyBoolean(), anyBoolean());
     }
 
     @Test
+    public void testSetAssistantAccess_doesNothingOnLowRam_exceptWatch() throws Exception {
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
+        when(mActivityManager.isLowRamDevice()).thenReturn(true);
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationAssistantAccessGranted(c, true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mListeners, never()).setPackageOrComponentEnabled(
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, false, true);
+        verify(mAssistants, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), 0, true, true);
+    }
+
+    @Test
+    public void testSetDndAccess_doesNothingOnLowRam_exceptWatch() throws Exception {
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
+        when(mActivityManager.isLowRamDevice()).thenReturn(true);
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mListeners, never()).setPackageOrComponentEnabled(
+                anyString(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.getPackageName(), 0, true, true);
+        verify(mAssistants, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
     public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 9ad7add..ea4f4b9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
         token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         token.setFillsParent(false);
-        // Can specify orientation if app doesn't fill parent. Allowed for SDK <= 25.
+        // Can specify orientation if app doesn't fill parent.
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
 
         token.setFillsParent(true);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
new file mode 100644
index 0000000..ad9aea7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.wm.proto.WindowManagerTraceProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Test class for {@link WindowTracing}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.WindowTracingTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowTracingTest extends WindowTestsBase {
+
+    private static final byte[] MAGIC_HEADER = new byte[] {
+        0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
+    };
+
+    private Context mTestContext;
+    private WindowTracing mWindowTracing;
+    private WindowManagerService mWmMock;
+    private File mFile;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mWmMock = mock(WindowManagerService.class);
+
+        mTestContext = InstrumentationRegistry.getContext();
+
+        mFile = mTestContext.getFileStreamPath("tracing_test.dat");
+        mFile.delete();
+
+        mWindowTracing = new WindowTracing(mFile);
+    }
+
+    @Test
+    public void isEnabled_returnsFalseByDefault() throws Exception {
+        assertFalse(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsTrueAfterStart() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        assertTrue(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsFalseAfterStop() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        mWindowTracing.stopTrace(mock(PrintWriter.class));
+        assertFalse(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void trace_discared_whenNotTracing() throws Exception {
+        mWindowTracing.traceStateLocked("where", mWmMock);
+        verifyZeroInteractions(mWmMock);
+    }
+
+    @Test
+    public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        mWindowTracing.traceStateLocked("where", mWmMock);
+
+        verify(mWmMock).writeToProtoLocked(any(), eq(true));
+    }
+
+    @Test
+    public void traceFile_startsWithMagicHeader() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+        mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+        byte[] header = new byte[MAGIC_HEADER.length];
+        try (InputStream is = new FileInputStream(mFile)) {
+            assertEquals(MAGIC_HEADER.length, is.read(header));
+            assertArrayEquals(MAGIC_HEADER, header);
+        }
+    }
+
+    @Test
+    @Ignore("Figure out why this test is crashing when setting up mWmMock.")
+    public void tracing_endsUpInFile() throws Exception {
+        mWindowTracing.startTrace(mock(PrintWriter.class));
+
+        doAnswer((inv) -> {
+            inv.<ProtoOutputStream>getArgument(0).write(
+                    WindowManagerTraceProto.WHERE, "TEST_WM_PROTO");
+            return null;
+        }).when(mWmMock).writeToProtoLocked(any(), any());
+        mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock);
+
+        mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+        byte[] file = new byte[1000];
+        int fileLength;
+        try (InputStream is = new FileInputStream(mFile)) {
+            fileLength = is.read(file);
+            assertTrue(containsBytes(file, fileLength,
+                    "TEST_WHERE".getBytes(StandardCharsets.UTF_8)));
+            assertTrue(containsBytes(file, fileLength,
+                    "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8)));
+        }
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        mFile.delete();
+    }
+
+    /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */
+    boolean containsBytes(byte[] haystack, int haystackLenght, byte[] needle) {
+        Preconditions.checkArgument(haystackLenght > 0);
+        Preconditions.checkArgument(needle.length > 0);
+
+        outer: for (int i = 0; i <= haystackLenght - needle.length; i++) {
+            for (int j = 0; j < needle.length; j++) {
+                if (haystack[i+j] != needle[j]) {
+                    continue outer;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Test
+    public void test_containsBytes() {
+        byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8);
+        assertTrue(containsBytes(haystack, haystack.length,
+                "hello".getBytes(StandardCharsets.UTF_8)));
+        assertTrue(containsBytes(haystack, haystack.length,
+                "world".getBytes(StandardCharsets.UTF_8)));
+        assertFalse(containsBytes(haystack, 6,
+                "world".getBytes(StandardCharsets.UTF_8)));
+        assertFalse(containsBytes(haystack, haystack.length,
+                "world_".getBytes(StandardCharsets.UTF_8)));
+        assertFalse(containsBytes(haystack, haystack.length,
+                "absent".getBytes(StandardCharsets.UTF_8)));
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 03d5f9d..3fc2208 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1543,6 +1543,13 @@
             "boosted_lte_earfcns_string_array";
 
     /**
+     * Determine whether to use only RSRP for the number of LTE signal bars.
+     * @hide
+     */
+    public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL =
+            "use_only_rsrp_for_lte_signal_bar_bool";
+
+    /**
      * Key identifying if voice call barring notification is required to be shown to the user.
      * @hide
      */
@@ -1902,6 +1909,7 @@
                 null);
         sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
         sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
         sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0);
         sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index c8b4776..de02de7 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -68,6 +68,7 @@
     private int mTdScdmaRscp;
 
     private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
+    private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
 
     /**
      * Create a new SignalStrength from a intent notifier Bundle
@@ -108,6 +109,7 @@
         mLteRsrpBoost = 0;
         mTdScdmaRscp = INVALID;
         isGsm = true;
+        mUseOnlyRsrpForLteLevel = false;
     }
 
     /**
@@ -134,6 +136,7 @@
         mLteRsrpBoost = 0;
         mTdScdmaRscp = INVALID;
         isGsm = gsmFlag;
+        mUseOnlyRsrpForLteLevel = false;
     }
 
     /**
@@ -145,10 +148,10 @@
             int cdmaDbm, int cdmaEcio,
             int evdoDbm, int evdoEcio, int evdoSnr,
             int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
-            int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) {
+            int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp);
         mTdScdmaRscp = tdScdmaRscp;
     }
 
@@ -164,7 +167,7 @@
             int tdScdmaRscp, boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
         mTdScdmaRscp = tdScdmaRscp;
     }
 
@@ -180,7 +183,7 @@
             boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
     }
 
     /**
@@ -194,7 +197,7 @@
             boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
-                INVALID, INVALID, INVALID, 0, gsmFlag);
+                INVALID, INVALID, INVALID, 0, gsmFlag, false);
     }
 
     /**
@@ -228,7 +231,7 @@
             boolean gsm) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
-                INVALID, INVALID, INVALID, 0, gsm);
+                INVALID, INVALID, INVALID, 0, gsm, false);
     }
 
     /**
@@ -248,6 +251,7 @@
      * @param lteCqi
      * @param lteRsrpBoost
      * @param gsm
+     * @param useOnlyRsrpForLteLevel
      *
      * @hide
      */
@@ -255,7 +259,7 @@
             int cdmaDbm, int cdmaEcio,
             int evdoDbm, int evdoEcio, int evdoSnr,
             int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
-            int lteRsrpBoost, boolean gsm) {
+            int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) {
         mGsmSignalStrength = gsmSignalStrength;
         mGsmBitErrorRate = gsmBitErrorRate;
         mCdmaDbm = cdmaDbm;
@@ -271,6 +275,7 @@
         mLteRsrpBoost = lteRsrpBoost;
         mTdScdmaRscp = INVALID;
         isGsm = gsm;
+        mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
         if (DBG) log("initialize: " + toString());
     }
 
@@ -293,6 +298,7 @@
         mLteRsrpBoost = s.mLteRsrpBoost;
         mTdScdmaRscp = s.mTdScdmaRscp;
         isGsm = s.isGsm;
+        mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
     }
 
     /**
@@ -318,6 +324,7 @@
         mLteRsrpBoost = in.readInt();
         mTdScdmaRscp = in.readInt();
         isGsm = (in.readInt() != 0);
+        mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
     }
 
     /**
@@ -366,6 +373,7 @@
         out.writeInt(mLteRsrpBoost);
         out.writeInt(mTdScdmaRscp);
         out.writeInt(isGsm ? 1 : 0);
+        out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
     }
 
     /**
@@ -449,6 +457,17 @@
     }
 
     /**
+     * @param useOnlyRsrpForLteLevel true if it uses only RSRP for the number of LTE signal bar,
+     * otherwise false.
+     *
+     * Used by phone to use only RSRP or not for the number of LTE signal bar.
+     * @hide
+     */
+    public void setUseOnlyRsrpForLteLevel(boolean useOnlyRsrpForLteLevel) {
+        mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+    }
+
+    /**
      * @param lteRsrpBoost - signal strength offset
      *
      * Used by phone to set the lte signal strength offset which will be
@@ -835,6 +854,13 @@
             }
         }
 
+        if (useOnlyRsrpForLteLevel()) {
+            log("getLTELevel - rsrp = " + rsrpIconLevel);
+            if (rsrpIconLevel != -1) {
+                return rsrpIconLevel;
+            }
+        }
+
         /*
          * Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5
          * dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars
@@ -915,6 +941,15 @@
     }
 
     /**
+     * @return true if it uses only RSRP for the number of LTE signal bar, otherwise false.
+     *
+     * @hide
+     */
+    public boolean useOnlyRsrpForLteLevel() {
+        return this.mUseOnlyRsrpForLteLevel;
+    }
+
+    /**
      * @return get TD_SCDMA dbm
      *
      * @hide
@@ -974,7 +1009,8 @@
                 + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
                 + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
                 + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
-                + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
+                + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
+                + (mUseOnlyRsrpForLteLevel ? 1 : 0));
     }
 
     /**
@@ -1008,7 +1044,8 @@
                 && mLteCqi == s.mLteCqi
                 && mLteRsrpBoost == s.mLteRsrpBoost
                 && mTdScdmaRscp == s.mTdScdmaRscp
-                && isGsm == s.isGsm);
+                && isGsm == s.isGsm
+                && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
     }
 
     /**
@@ -1031,7 +1068,9 @@
                 + " " + mLteCqi
                 + " " + mLteRsrpBoost
                 + " " + mTdScdmaRscp
-                + " " + (isGsm ? "gsm|lte" : "cdma"));
+                + " " + (isGsm ? "gsm|lte" : "cdma")
+                + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
+                         "use_rsrp_and_rssnr_for_lte_level"));
     }
 
     /** Returns the signal strength related to GSM. */
@@ -1086,6 +1125,7 @@
         mLteRsrpBoost = m.getInt("lteRsrpBoost");
         mTdScdmaRscp = m.getInt("TdScdma");
         isGsm = m.getBoolean("isGsm");
+        mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
     }
 
     /**
@@ -1110,6 +1150,7 @@
         m.putInt("lteRsrpBoost", mLteRsrpBoost);
         m.putInt("TdScdma", mTdScdmaRscp);
         m.putBoolean("isGsm", isGsm);
+        m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 67f6849..42c3de5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2166,13 +2166,16 @@
      * @hide
      */
     public String getSimOperatorNumeric() {
-        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        int subId = mSubId;
         if (!SubscriptionManager.isUsableSubIdValue(subId)) {
-            subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+            subId = SubscriptionManager.getDefaultDataSubscriptionId();
             if (!SubscriptionManager.isUsableSubIdValue(subId)) {
-                subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                subId = SubscriptionManager.getDefaultSmsSubscriptionId();
                 if (!SubscriptionManager.isUsableSubIdValue(subId)) {
-                    subId = SubscriptionManager.getDefaultSubscriptionId();
+                    subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                    if (!SubscriptionManager.isUsableSubIdValue(subId)) {
+                        subId = SubscriptionManager.getDefaultSubscriptionId();
+                    }
                 }
             }
         }
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index d8d7f48..b30a3af 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,6 +36,7 @@
 
     /** @hide */
     @SystemApi
+    @TestApi
     public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
             List<Locale> newLocales, String newServiceId, Date start, Date end,
             List<FileInfo> newFiles) {
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 9ccdd56..4fee3df 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.IBinder;
@@ -42,6 +43,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
     private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
     private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 62d570c..99a82ad 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -567,4 +567,12 @@
         } while (valueIndex < endIndex);
         return result;
     }
+
+    public static String getDecimalSubstring(String iccId) {
+        int position;
+        for (position = 0; position < iccId.length(); position ++) {
+            if (!Character.isDigit(iccId.charAt(position))) break;
+        }
+        return iccId.substring( 0, position );
+    }
 }
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index f6006b0..6435ad9 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -15,9 +15,11 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.testables">
+    package="com.android.testables" android:sharedUserId="android.uid.system">
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 135df40..674bee1 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -18,6 +18,7 @@
 #include <frameworks/base/core/proto/android/os/incident.pb.h>
 
 #include <map>
+#include <set>
 #include <string>
 
 using namespace android;
@@ -27,6 +28,60 @@
 using namespace google::protobuf::internal;
 using namespace std;
 
+/**
+ * Implementation details:
+ * This binary auto generates .cpp files for incident and incidentd.
+ *
+ * When argument "incident" is specified, it generates incident_section.cpp file.
+ *
+ * When argument "incidentd" is specified, it generates section_list.cpp file.
+ *
+ * In section_list.cpp file, it generates a SECTION_LIST array and a PRIVACY_POLICY_LIST array.
+ * For SECTION_LIST, it generates Section.h classes only for proto fields with section option enabled.
+ * For PRIVACY_POLICY_LIST, it generates Privacy.h classes only for proto fields with privacy option enabled.
+ *
+ * For Privacy struct, it is possible to have self recursion definitions since protobuf is defining "classes"
+ * So the logic to handle it becomes very complicated when Privacy tag of a message contains a list of Privacies
+ * of its sub-messages. The code also handles multiple depth of self recursion fields.
+ *
+ * For example here is a one level self recursion message WindowManager:
+ * message WindowState {
+ *     string state = 1 [(privacy).dest = LOCAL];
+ *     int32  display_id = 2;
+ *     repeated WindowState child_windows = 3;
+ * }
+ *
+ * message WindowManager {
+ *     WindowState my_window = 1;
+ * }
+ *
+ * When generating Privacy options for WindowManager, this tool will generate cpp syntax source code:
+ *
+ * #include "section_list.h"
+ * ...
+ * Privacy WindowState_state { 1, 9, NULL, LOCAL, NULL }; // first two integers are values for field id and proto type.
+ * Privacy WindowState_child_windows { 3, 11, NULL, DEFAULT, NULL }; // reserved for WindowState_LIST
+ * Privacy* WindowState_MSG_[] = {
+ *     &WindowState_state,
+ *     // display id is default, nothing is generated.
+ *     &WindowState_child_windows,
+ *     NULL  // terminator of the array
+ * };
+ * Privacy WindowState_my_window { 1, 11, WindowState_my_window_LIST, DEFAULT, NULL };
+ *
+ * createList() {
+ *    ...
+ *    WindowState_child_windows.children = WindowState_my_window_LIST; // point to its own definition after the list is defined.
+ *    ...
+ * }
+ *
+ * const Privacy** PRIVACY_POLICY_LIST = createList();
+ * const int PRIVACY_POLICY_COUNT = 1;
+ */
+
+// The assignments will be called when constructs PRIVACY_POLICY_LIST, has to be global variable
+vector<string> gSelfRecursionAssignments;
+
 static inline void emptyline() {
     printf("\n");
 }
@@ -38,7 +93,7 @@
     emptyline();
 }
 
-// ================================================================================
+// ======================== incident_sections =============================
 static bool generateIncidentSectionsCpp(Descriptor const* descriptor)
 {
     generateHead("incident_sections");
@@ -73,7 +128,7 @@
     return true;
 }
 
-// ================================================================================
+// ========================= section_list ===================================
 static void splitAndPrint(const string& args) {
     size_t base = 0;
     size_t found;
@@ -88,12 +143,12 @@
     }
 }
 
-static const std::string replaceAll(const string& field_name, const char oldC, const string& newS) {
-    if (field_name.find_first_of(oldC) == field_name.npos) return field_name.c_str();
+static string replaceAll(const string& fieldName, const char oldC, const string& newS) {
+    if (fieldName.find_first_of(oldC) == fieldName.npos) return fieldName.c_str();
     size_t pos = 0, idx = 0;
-    char* res = new char[field_name.size() * newS.size() + 1]; // assign a larger buffer
-    while (pos != field_name.size()) {
-        char cur = field_name[pos++];
+    char* res = new char[fieldName.size() * newS.size() + 1]; // assign a larger buffer
+    while (pos != fieldName.size()) {
+        char cur = fieldName[pos++];
         if (cur != oldC) {
             res[idx++] = cur;
             continue;
@@ -104,92 +159,162 @@
         }
     }
     res[idx] = '\0';
-    std::string result(res);
+    string result(res);
     delete [] res;
     return result;
 }
 
-static inline bool isDefaultDest(const FieldDescriptor* field) {
-    return field->options().GetExtension(privacy).dest() == PrivacyFlags::default_instance().dest();
+static string getFieldName(const FieldDescriptor* field) {
+    return replaceAll(field->full_name(), '.', "__");
 }
 
+static string getMessageTypeName(const Descriptor* descriptor) {
+    return replaceAll(descriptor->full_name(), '.', "_") + "_MSG_";
+}
+
+static inline SectionFlags getSectionFlags(const FieldDescriptor* field) {
+    return field->options().GetExtension(section);
+}
+
+static inline PrivacyFlags getPrivacyFlags(const FieldDescriptor* field) {
+    return field->options().GetExtension(privacy);
+}
+
+static inline bool isDefaultField(const FieldDescriptor* field) {
+    return getPrivacyFlags(field).dest() == PrivacyFlags::default_instance().dest();
+}
+
+static bool isDefaultMessageImpl(const Descriptor* descriptor, set<string>* parents) {
+    int N = descriptor->field_count();
+    parents->insert(descriptor->full_name());
+    for (int i=0; i<N; ++i) {
+        const FieldDescriptor* field = descriptor->field(i);
+        // look at if the current field is default or not, return false immediately
+        if (!isDefaultField(field)) return false;
+
+        switch (field->type()) {
+            case FieldDescriptor::TYPE_MESSAGE:
+                // if self recursion, don't go deep.
+                if (parents->find(field->message_type()->full_name()) != parents->end()) break;
+                // if is a default message, just continue
+                if (isDefaultMessageImpl(field->message_type(), parents)) break;
+                // sub message is not default, so this message is always not default
+                return false;
+            case FieldDescriptor::TYPE_STRING:
+                if (getPrivacyFlags(field).patterns_size() != 0) return false;
+            default:
+                continue;
+        }
+    }
+    parents->erase(descriptor->full_name());
+    return true;
+}
+
+static bool isDefaultMessage(const Descriptor* descriptor) {
+    set<string> parents;
+    return isDefaultMessageImpl(descriptor, &parents);
+}
+
+// This function is called for looking at privacy tags for a message type and recursively its sub-messages
+// It prints out each fields's privacy tags and a List of Privacy of the message itself (don't print default values)
 // Returns false if the descriptor doesn't have any non default privacy flags set, including its submessages
-static bool generatePrivacyFlags(const Descriptor* descriptor, const char* alias, map<string, bool> &msgNames) {
+static bool generatePrivacyFlags(const Descriptor* descriptor, map<string, bool> &msgNames, set<string>* parents) {
     bool hasDefaultFlags[descriptor->field_count()];
+
+    string messageTypeName = getMessageTypeName(descriptor);
+    // if the message is already defined, skip it.
+    if (msgNames.find(messageTypeName) != msgNames.end()) {
+        bool hasDefault = msgNames[messageTypeName];
+        return !hasDefault; // don't generate if it has default privacy.
+    }
+    // insert the message type name so sub-message will figure out if self-recursion occurs
+    parents->insert(messageTypeName);
+
     // iterate though its field and generate sub flags first
     for (int i=0; i<descriptor->field_count(); i++) {
         hasDefaultFlags[i] = true; // set default to true
+
         const FieldDescriptor* field = descriptor->field(i);
-        const std::string field_name_str = replaceAll(field->full_name(), '.', "__");
-        const char* field_name = field_name_str.c_str();
-        // check if the same name is already defined
-        if (msgNames.find(field_name) != msgNames.end()) {
-            hasDefaultFlags[i] = msgNames[field_name];
+        const string fieldName = getFieldName(field);
+        // check if the same field name is already defined.
+        if (msgNames.find(fieldName) != msgNames.end()) {
+            hasDefaultFlags[i] = msgNames[fieldName];
             continue;
         };
 
-        PrivacyFlags p = field->options().GetExtension(privacy);
+        PrivacyFlags p = getPrivacyFlags(field);
+        string fieldMessageName;
         switch (field->type()) {
             case FieldDescriptor::TYPE_MESSAGE:
-                if (generatePrivacyFlags(field->message_type(), field_name, msgNames)) {
-                    printf("Privacy %s { %d, %d, %s_LIST, %d, NULL };\n", field_name, field->number(),
-                            field->type(), field_name, p.dest());
-                } else if (isDefaultDest(field)) {
+                fieldMessageName = getMessageTypeName(field->message_type());
+                if (parents->find(fieldMessageName) != parents->end()) { // Self-Recursion proto definition
+                    if (isDefaultField(field)) {
+                        hasDefaultFlags[i] = isDefaultMessage(field->message_type());
+                    } else {
+                        hasDefaultFlags[i] = false;
+                    }
+                    if (!hasDefaultFlags[i]) {
+                        printf("Privacy %s = { %d, %d, NULL, %d, NULL }; // self recursion field of %s\n",
+                                fieldName.c_str(), field->number(), field->type(), p.dest(), fieldMessageName.c_str());
+                        // generate the assignment and used to construct createList function later on.
+                        gSelfRecursionAssignments.push_back(fieldName + ".children = " + fieldMessageName);
+                    }
+                    break;
+                } else if (generatePrivacyFlags(field->message_type(), msgNames, parents)) {
+                    printf("Privacy %s = { %d, %d, %s, %d, NULL };\n", fieldName.c_str(), field->number(),
+                            field->type(), fieldMessageName.c_str(), p.dest());
+                } else if (isDefaultField(field)) {
                     // don't create a new privacy if the value is default.
                     break;
-                } else{
-                    printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(),
+                } else {
+                    printf("Privacy %s = { %d, %d, NULL, %d, NULL };\n", fieldName.c_str(), field->number(),
                             field->type(), p.dest());
                 }
                 hasDefaultFlags[i] = false;
                 break;
             case FieldDescriptor::TYPE_STRING:
-                if (isDefaultDest(field) && p.patterns_size() == 0) break;
+                if (isDefaultField(field) && p.patterns_size() == 0) break;
 
-                printf("const char* %s_patterns[] = {\n", field_name);
+                printf("const char* %s_patterns[] = {\n", fieldName.c_str());
                 for (int i=0; i<p.patterns_size(); i++) {
                     // the generated string need to escape backslash as well, need to dup it here
                     printf("    \"%s\",\n", replaceAll(p.patterns(i), '\\', "\\\\").c_str());
                 }
                 printf("    NULL };\n");
-                printf("Privacy %s { %d, %d, NULL, %d, %s_patterns };\n", field_name, field->number(),
-                        field->type(), p.dest(), field_name);
+                printf("Privacy %s = { %d, %d, NULL, %d, %s_patterns };\n", fieldName.c_str(), field->number(),
+                        field->type(), p.dest(), fieldName.c_str());
                 hasDefaultFlags[i] = false;
                 break;
             default:
-                if (isDefaultDest(field)) break;
-                printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(),
+                if (isDefaultField(field)) break;
+                printf("Privacy %s = { %d, %d, NULL, %d, NULL };\n", fieldName.c_str(), field->number(),
                         field->type(), p.dest());
                 hasDefaultFlags[i] = false;
         }
         // add the field name to message map, true means it has default flags
-        msgNames[field_name] = hasDefaultFlags[i];
+        msgNames[fieldName] = hasDefaultFlags[i];
     }
 
     bool allDefaults = true;
     for (int i=0; i<descriptor->field_count(); i++) {
         allDefaults &= hasDefaultFlags[i];
     }
+
+    parents->erase(messageTypeName); // erase the message type name when exit the message.
+    msgNames[messageTypeName] = allDefaults; // store the privacy tags of the message here to avoid overhead.
+
     if (allDefaults) return false;
 
     emptyline();
-
-    bool needConst = strcmp(alias, "PRIVACY_POLICY") == 0;
     int policyCount = 0;
-
-    printf("%sPrivacy* %s_LIST[] = {\n", needConst ? "const " : "", alias);
+    printf("Privacy* %s[] = {\n", messageTypeName.c_str());
     for (int i=0; i<descriptor->field_count(); i++) {
         const FieldDescriptor* field = descriptor->field(i);
         if (hasDefaultFlags[i]) continue;
-        printf("    &%s,\n", replaceAll(field->full_name(), '.', "__").c_str());
+        printf("    &%s,\n", getFieldName(field).c_str());
         policyCount++;
     }
-    if (needConst) {
-        printf("};\n\n");
-        printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount);
-    } else {
-        printf("    NULL };\n");
-    }
+    printf("    NULL };\n");
     emptyline();
     return true;
 }
@@ -198,6 +323,8 @@
     generateHead("section_list");
 
     // generates SECTION_LIST
+    printf("// Generate SECTION_LIST.\n\n");
+
     printf("const Section* SECTION_LIST[] = {\n");
     for (int i=0; i<descriptor->field_count(); i++) {
         const FieldDescriptor* field = descriptor->field(i);
@@ -205,7 +332,7 @@
         if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
             continue;
         }
-        const SectionFlags s = field->options().GetExtension(section);
+        const SectionFlags s = getSectionFlags(field);
         switch (s.type()) {
             case SECTION_NONE:
                 continue;
@@ -225,16 +352,73 @@
         }
     }
     printf("    NULL };\n");
+
+    emptyline();
+    printf("// =============================================================================\n");
     emptyline();
 
-    // generates PRIVACY_POLICY
+    // generates PRIVACY_POLICY_LIST
+    printf("// Generate PRIVACY_POLICY_LIST.\n\n");
     map<string, bool> messageNames;
-    if (!generatePrivacyFlags(descriptor, "PRIVACY_POLICY", messageNames)) {
-        // if no privacy options set at all, define an empty list
-        printf("const Privacy* PRIVACY_POLICY_LIST[] = {};\n");
-        printf("const int PRIVACY_POLICY_COUNT = 0;\n");
+    set<string> parents;
+    bool skip[descriptor->field_count()];
+
+    for (int i=0; i<descriptor->field_count(); i++) {
+        const FieldDescriptor* field = descriptor->field(i);
+        const string fieldName = getFieldName(field);
+        PrivacyFlags p = getPrivacyFlags(field);
+
+        skip[i] = true;
+
+        if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+            continue;
+        }
+        // generate privacy flags for each field.
+        if (generatePrivacyFlags(field->message_type(), messageNames, &parents)) {
+            printf("Privacy %s { %d, %d, %s, %d, NULL };\n", fieldName.c_str(), field->number(),
+                    field->type(), getMessageTypeName(field->message_type()).c_str(), p.dest());
+        } else if (isDefaultField(field)) {
+            continue; // don't create a new privacy if the value is default.
+        } else {
+            printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", fieldName.c_str(), field->number(),
+                    field->type(), p.dest());
+        }
+        skip[i] = false;
     }
 
+    // generate final PRIVACY_POLICY_LIST
+    emptyline();
+    int policyCount = 0;
+    if (gSelfRecursionAssignments.empty()) {
+        printf("Privacy* privacyArray[] = {\n");
+        for (int i=0; i<descriptor->field_count(); i++) {
+            if (skip[i]) continue;
+            printf("    &%s,\n", getFieldName(descriptor->field(i)).c_str());
+            policyCount++;
+        }
+        printf("};\n\n");
+        printf("const Privacy** PRIVACY_POLICY_LIST = const_cast<const Privacy**>(privacyArray);\n\n");
+        printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount);
+    } else {
+        for (int i=0; i<descriptor->field_count(); i++) {
+            if (!skip[i]) policyCount++;
+        }
+
+        printf("static const Privacy** createList() {\n");
+        for (size_t i=0; i<gSelfRecursionAssignments.size(); ++i) {
+            printf("    %s;\n", gSelfRecursionAssignments[i].c_str());
+        }
+        printf("    Privacy** privacyArray = (Privacy**)malloc(%d * sizeof(Privacy**));\n", policyCount);
+        policyCount = 0; // reset
+        for (int i=0; i<descriptor->field_count(); i++) {
+            if (skip[i]) continue;
+            printf("    privacyArray[%d] = &%s;\n", policyCount++, getFieldName(descriptor->field(i)).c_str());
+        }
+        printf("    return const_cast<const Privacy**>(privacyArray);\n");
+        printf("}\n\n");
+        printf("const Privacy** PRIVACY_POLICY_LIST = createList();\n\n");
+        printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount);
+    }
     return true;
 }
 
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 5d29268..f76196d 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -22,6 +22,7 @@
 namespace android {
 namespace stats_log_api_gen {
 
+using google::protobuf::EnumDescriptor;
 using google::protobuf::FieldDescriptor;
 using google::protobuf::FileDescriptor;
 using google::protobuf::SourceLocation;
@@ -120,7 +121,7 @@
         case FieldDescriptor::TYPE_UINT32:
             return JAVA_TYPE_INT;
         case FieldDescriptor::TYPE_ENUM:
-            return JAVA_TYPE_INT;
+            return JAVA_TYPE_ENUM;
         case FieldDescriptor::TYPE_SFIXED32:
             return JAVA_TYPE_INT;
         case FieldDescriptor::TYPE_SFIXED64:
@@ -208,7 +209,6 @@
                 errorCount++;
                 continue;
             }
-
         }
 
         // Check that if there's a WorkSource, it's at position 1.
@@ -228,15 +228,26 @@
 
         AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
 
-        // Build the type signature
+        // Build the type signature and the atom data.
         vector<java_type_t> signature;
         for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
                 it != fields.end(); it++) {
             const FieldDescriptor* field = it->second;
             java_type_t javaType = java_type(field);
 
-            atomDecl.fields.push_back(AtomField(field->name(), javaType));
-            signature.push_back(javaType);
+            AtomField atField(field->name(), javaType);
+            if (javaType == JAVA_TYPE_ENUM) {
+                // All enums are treated as ints when it comes to function signatures.
+                signature.push_back(JAVA_TYPE_INT);
+                const EnumDescriptor* enumDescriptor = field->enum_type();
+                for (int i = 0; i < enumDescriptor->value_count(); i++) {
+                    atField.enumValues[enumDescriptor->value(i)->number()] =
+                        enumDescriptor->value(i)->name().c_str();
+                }
+            } else {
+                signature.push_back(javaType);
+            }
+            atomDecl.fields.push_back(atField);
         }
 
         atoms->signatures.insert(signature);
@@ -261,5 +272,3 @@
 
 }  // namespace stats_log_api_gen
 }  // namespace android
-
-
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 50af7ea..2f840d7 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -22,10 +22,12 @@
 
 #include <set>
 #include <vector>
+#include <map>
 
 namespace android {
 namespace stats_log_api_gen {
 
+using std::map;
 using std::set;
 using std::string;
 using std::vector;
@@ -44,6 +46,7 @@
     JAVA_TYPE_FLOAT = 5,
     JAVA_TYPE_DOUBLE = 6,
     JAVA_TYPE_STRING = 7,
+    JAVA_TYPE_ENUM = 8,
 
     JAVA_TYPE_OBJECT = -1,
     JAVA_TYPE_BYTE_ARRAY = -2,
@@ -57,8 +60,13 @@
     string name;
     java_type_t javaType;
 
+    // If the field is of type enum, the following map contains the list of enum values.
+    map<int /* numeric value */, string /* value name */> enumValues;
+
     inline AtomField() :name(), javaType(JAVA_TYPE_UNKNOWN) {}
-    inline AtomField(const AtomField& that) :name(that.name), javaType(that.javaType) {}
+    inline AtomField(const AtomField& that) :name(that.name),
+                                             javaType(that.javaType),
+                                             enumValues(that.enumValues) {}
     inline AtomField(string n, java_type_t jt) :name(n), javaType(jt) {}
     inline ~AtomField() {}
 };
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index aaea4f6..6350b72 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -56,6 +56,7 @@
         case JAVA_TYPE_BOOLEAN:
             return "bool";
         case JAVA_TYPE_INT:
+        case JAVA_TYPE_ENUM:
             return "int32_t";
         case JAVA_TYPE_LONG:
             return "int64_t";
@@ -77,6 +78,7 @@
         case JAVA_TYPE_BOOLEAN:
             return "boolean";
         case JAVA_TYPE_INT:
+        case JAVA_TYPE_ENUM:
             return "int";
         case JAVA_TYPE_LONG:
             return "long";
@@ -173,7 +175,7 @@
     fprintf(out, " */\n");
     fprintf(out, "\n");
     fprintf(out, "/**\n");
-    fprintf(out, " * Constants for event codes.\n");
+    fprintf(out, " * Constants for atom codes.\n");
     fprintf(out, " */\n");
     fprintf(out, "enum {\n");
 
@@ -240,9 +242,9 @@
     fprintf(out, " * @hide\n");
     fprintf(out, " */\n");
     fprintf(out, "public final class StatsLog {\n");
-    fprintf(out, "    // Constants for event codes.\n");
+    fprintf(out, "    // Constants for atom codes.\n");
 
-    // Print constants
+    // Print constants for the atom codes.
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
             atom != atoms.decls.end(); atom++) {
         string constant = make_constant_name(atom->name);
@@ -260,6 +262,27 @@
     }
     fprintf(out, "\n");
 
+    // Print constants for the enum values.
+    fprintf(out, "    // Constants for enum values.\n\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+            atom != atoms.decls.end(); atom++) {
+        for (vector<AtomField>::const_iterator field = atom->fields.begin();
+                field != atom->fields.end(); field++) {
+          if (field->javaType == JAVA_TYPE_ENUM) {
+            fprintf(out, "    // Values for %s.%s\n", atom->message.c_str(), field->name.c_str());
+            for (map<int, string>::const_iterator value = field->enumValues.begin();
+                 value != field->enumValues.end(); value++) {
+              fprintf(out, "    public static final int %s__%s__%s = %d;\n",
+                      make_constant_name(atom->message).c_str(),
+                      make_constant_name(field->name).c_str(),
+                      make_constant_name(value->second).c_str(),
+                      value->first);
+            }
+            fprintf(out, "\n");
+          }
+        }
+    }
+
     // Print write methods
     fprintf(out, "    // Write methods\n");
     for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
@@ -286,6 +309,7 @@
         case JAVA_TYPE_BOOLEAN:
             return "jboolean";
         case JAVA_TYPE_INT:
+        case JAVA_TYPE_ENUM:
             return "jint";
         case JAVA_TYPE_LONG:
             return "jlong";
@@ -311,6 +335,7 @@
                 result += "_boolean";
                 break;
             case JAVA_TYPE_INT:
+            case JAVA_TYPE_ENUM:
                 result += "_int";
                 break;
             case JAVA_TYPE_LONG:
@@ -340,6 +365,7 @@
         case JAVA_TYPE_BOOLEAN:
             return "Z";
         case JAVA_TYPE_INT:
+        case JAVA_TYPE_ENUM:
             return "I";
         case JAVA_TYPE_LONG:
             return "J";
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index 1bd2e3d..073f2cf 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -24,6 +24,7 @@
 namespace android {
 namespace stats_log_api_gen {
 
+using std::map;
 using std::set;
 using std::vector;
 
@@ -54,6 +55,29 @@
         EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \
     } while(0)
 
+/** Expects that the provided atom has no enum values for any field. */
+#define EXPECT_NO_ENUM_FIELD(atom) \
+    do { \
+        for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
+             field != atom->fields.end(); field++) { \
+            EXPECT_TRUE(field->enumValues.empty()); \
+        } \
+    } while(0)
+
+/** Expects that exactly one specific field has expected enum values. */ 
+#define EXPECT_HAS_ENUM_FIELD(atom, field_name, values)        \
+    do { \
+        for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
+             field != atom->fields.end(); field++) { \
+            if (field->name == field_name) { \
+                EXPECT_EQ(field->enumValues, values); \
+            } else { \
+                EXPECT_TRUE(field->enumValues.empty()); \
+            } \
+        } \
+    } while(0)
+
+
 /**
  * Test a correct collation, with all the types.
  */
@@ -94,21 +118,28 @@
     EXPECT_EQ(1, atom->code);
     EXPECT_EQ("int_atom", atom->name);
     EXPECT_EQ("IntAtom", atom->message);
+    EXPECT_NO_ENUM_FIELD(atom);
     atom++;
 
     EXPECT_EQ(2, atom->code);
     EXPECT_EQ("out_of_order_atom", atom->name);
     EXPECT_EQ("OutOfOrderAtom", atom->message);
+    EXPECT_NO_ENUM_FIELD(atom);
     atom++;
 
     EXPECT_EQ(3, atom->code);
     EXPECT_EQ("another_int_atom", atom->name);
     EXPECT_EQ("AnotherIntAtom", atom->message);
+    EXPECT_NO_ENUM_FIELD(atom);
     atom++;
 
     EXPECT_EQ(4, atom->code);
     EXPECT_EQ("all_types_atom", atom->name);
     EXPECT_EQ("AllTypesAtom", atom->message);
+    map<int, string> enumValues;
+    enumValues[0] = "VALUE0";
+    enumValues[1] = "VALUE1";
+    EXPECT_HAS_ENUM_FIELD(atom, "enum_field", enumValues);
     atom++;
 
     EXPECT_TRUE(atom == atoms.decls.end());
@@ -125,7 +156,7 @@
 }
 
 /**
- * Test that atoms that have non-primitve types are rejected.
+ * Test that atoms that have non-primitive types are rejected.
  */
 TEST(CollationTest, FailOnBadTypes) {
     Atoms atoms;
@@ -165,7 +196,5 @@
     EXPECT_EQ(1, errorCount);
 }
 
-
 }  // namespace stats_log_api_gen
 }  // namespace android
-
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 756549c..dc5c14e 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -21,10 +21,32 @@
     name: "protoc-gen-stream-defaults",
     srcs: [
         "Errors.cpp",
+        "stream_proto_utils.cpp",
         "string_utils.cpp",
     ],
+
+    shared_libs: ["libprotoc"],
 }
 
+cc_library {
+    name: "streamingflags",
+    host_supported: true,
+    proto: {
+        export_proto_headers: true,
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    target: {
+        host: {
+            proto: {
+                type: "full",
+            },
+            srcs: [
+                "stream.proto",
+            ],
+        },
+    },
+}
 
 cc_binary_host {
     name: "protoc-gen-javastream",
@@ -33,7 +55,6 @@
     ],
 
     defaults: ["protoc-gen-stream-defaults"],
-    shared_libs: ["libprotoc"],
 }
 
 cc_binary_host {
@@ -43,5 +64,5 @@
     ],
 
     defaults: ["protoc-gen-stream-defaults"],
-    shared_libs: ["libprotoc"],
+    static_libs: ["streamingflags"],
 }
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index d4e1b7a..4816984 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -1,106 +1,23 @@
 #include "Errors.h"
+#include "stream_proto_utils.h"
 #include "string_utils.h"
 
-#include "google/protobuf/compiler/plugin.pb.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
+#include <frameworks/base/tools/streaming_proto/stream.pb.h>
 
 #include <iomanip>
 #include <iostream>
 #include <sstream>
 
 using namespace android::stream_proto;
-using namespace google::protobuf;
-using namespace google::protobuf::compiler;
 using namespace google::protobuf::io;
 using namespace std;
 
-/**
- * Position of the field type in a (long long) fieldId.
- */
-const uint64_t FIELD_TYPE_SHIFT = 32;
-
-//
-// FieldId flags for whether the field is single, repeated or packed.
-// TODO: packed is not supported yet.
-//
-const uint64_t FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_UNKNOWN = 0;
-const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
-
-// Indent
-const string INDENT = "    ";
-
-/**
- * See if this is the file for this request, and not one of the imported ones.
- */
-static bool
-should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
-{
-    const int N = request.file_to_generate_size();
-    for (int i=0; i<N; i++) {
-        if (request.file_to_generate(i) == file) {
-            return true;
-        }
-    }
-    return false;
-}
-
 static string
 make_filename(const FileDescriptorProto& file_descriptor)
 {
     return file_descriptor.name() + ".h";
 }
 
-static string
-get_proto_type(const FieldDescriptorProto& field)
-{
-    switch (field.type()) {
-        case FieldDescriptorProto::TYPE_DOUBLE:
-            return "double";
-        case FieldDescriptorProto::TYPE_FLOAT:
-            return "float";
-        case FieldDescriptorProto::TYPE_INT64:
-            return "int64";
-        case FieldDescriptorProto::TYPE_UINT64:
-            return "uint64";
-        case FieldDescriptorProto::TYPE_INT32:
-            return "int32";
-        case FieldDescriptorProto::TYPE_FIXED64:
-            return "fixed64";
-        case FieldDescriptorProto::TYPE_FIXED32:
-            return "fixed32";
-        case FieldDescriptorProto::TYPE_BOOL:
-            return "bool";
-        case FieldDescriptorProto::TYPE_STRING:
-            return "string";
-        case FieldDescriptorProto::TYPE_GROUP:
-            return "group<unsupported!>";
-        case FieldDescriptorProto::TYPE_MESSAGE:
-            return field.type_name();
-        case FieldDescriptorProto::TYPE_BYTES:
-            return "bytes";
-        case FieldDescriptorProto::TYPE_UINT32:
-            return "uint32";
-        case FieldDescriptorProto::TYPE_ENUM:
-            return field.type_name();
-        case FieldDescriptorProto::TYPE_SFIXED32:
-            return "sfixed32";
-        case FieldDescriptorProto::TYPE_SFIXED64:
-            return "sfixed64";
-        case FieldDescriptorProto::TYPE_SINT32:
-            return "sint32";
-        case FieldDescriptorProto::TYPE_SINT64:
-            return "sint64";
-        default:
-            // won't happen
-            return "void";
-    }
-}
-
 static void
 write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
 {
@@ -115,27 +32,6 @@
     text << endl;
 }
 
-static uint64_t
-get_field_id(const FieldDescriptorProto& field)
-{
-    // Number
-    uint64_t result = (uint64_t)field.number();
-
-    // Type
-    result |= (uint64_t)field.type() << FIELD_TYPE_SHIFT;
-
-    // Count
-    if (field.options().packed()) {
-        result |= FIELD_COUNT_PACKED;
-    } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
-        result |= FIELD_COUNT_REPEATED;
-    } else {
-        result |= FIELD_COUNT_SINGLE;
-    }
-
-    return result;
-}
-
 static void
 write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
 {
@@ -160,6 +56,12 @@
     text << endl;
 }
 
+static inline bool
+should_generate_fields_mapping(const DescriptorProto& message)
+{
+    return message.options().GetExtension(stream).enable_fields_mapping();
+}
+
 static void
 write_message(stringstream& text, const DescriptorProto& message, const string& indent)
 {
@@ -167,8 +69,7 @@
     const string indented = indent + INDENT;
 
     text << indent << "// message " << message.name() << endl;
-    text << indent << "class " << message.name() << " {" << endl;
-    text << indent << "public:" << endl;
+    text << indent << "namespace " << message.name() << " {" << endl;
 
     // Enums
     N = message.enum_type_size();
@@ -188,12 +89,27 @@
         write_field(text, message.field(i), indented);
     }
 
-    text << indent << "};" << endl;
+    if (should_generate_fields_mapping(message)) {
+        N = message.field_size();
+        text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
+        text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
+        for (int i=0; i<N; i++) {
+            text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl;
+        }
+        text << indented << "};" << endl;
+        text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
+        for (int i=0; i<N; i++) {
+            text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl;
+        }
+        text << indented << "};" << endl << endl;
+    }
+
+    text << indent << "} //" << message.name() << endl;
     text << endl;
 }
 
 static void
-write_cpp_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
 {
     stringstream text;
 
@@ -255,7 +171,7 @@
     for (int i=0; i<N; i++) {
         const FileDescriptorProto& file_descriptor = request.proto_file(i);
         if (should_generate_for_file(request, file_descriptor.name())) {
-            write_cpp_file(&response, file_descriptor);
+            write_header_file(&response, file_descriptor);
         }
     }
 
@@ -270,4 +186,4 @@
 
     /* code */
     return 0;
-}
\ No newline at end of file
+}
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index b7d594b..c9c50a5 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,11 +1,7 @@
 #include "Errors.h"
-
+#include "stream_proto_utils.h"
 #include "string_utils.h"
 
-#include "google/protobuf/compiler/plugin.pb.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
-
 #include <stdio.h>
 #include <iomanip>
 #include <iostream>
@@ -13,51 +9,9 @@
 #include <map>
 
 using namespace android::stream_proto;
-using namespace google::protobuf;
-using namespace google::protobuf::compiler;
 using namespace google::protobuf::io;
 using namespace std;
 
-const int FIELD_TYPE_SHIFT = 32;
-const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
-
-const int FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
-
-
-/**
- * See if this is the file for this request, and not one of the imported ones.
- */
-static bool
-should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
-{
-    const int N = request.file_to_generate_size();
-    for (int i=0; i<N; i++) {
-        if (request.file_to_generate(i) == file) {
-            return true;
-        }
-    }
-    return false;
-}
-
 /**
  * If the descriptor gives us a class name, use that. Otherwise make one up from
  * the filename of the .proto file.
@@ -112,7 +66,7 @@
 static string
 indent_more(const string& indent)
 {
-    return indent + "    ";
+    return indent + INDENT;
 }
 
 /**
@@ -133,130 +87,6 @@
 }
 
 /**
- * Get the string name for a field.
- */
-static string
-get_proto_type(const FieldDescriptorProto& field)
-{
-    switch (field.type()) {
-        case FieldDescriptorProto::TYPE_DOUBLE:
-            return "double";
-        case FieldDescriptorProto::TYPE_FLOAT:
-            return "float";
-        case FieldDescriptorProto::TYPE_INT64:
-            return "int64";
-        case FieldDescriptorProto::TYPE_UINT64:
-            return "uint64";
-        case FieldDescriptorProto::TYPE_INT32:
-            return "int32";
-        case FieldDescriptorProto::TYPE_FIXED64:
-            return "fixed64";
-        case FieldDescriptorProto::TYPE_FIXED32:
-            return "fixed32";
-        case FieldDescriptorProto::TYPE_BOOL:
-            return "bool";
-        case FieldDescriptorProto::TYPE_STRING:
-            return "string";
-        case FieldDescriptorProto::TYPE_GROUP:
-            return "group<unsupported!>";
-        case FieldDescriptorProto::TYPE_MESSAGE:
-            return field.type_name();
-        case FieldDescriptorProto::TYPE_BYTES:
-            return "bytes";
-        case FieldDescriptorProto::TYPE_UINT32:
-            return "uint32";
-        case FieldDescriptorProto::TYPE_ENUM:
-            return field.type_name();
-        case FieldDescriptorProto::TYPE_SFIXED32:
-            return "sfixed32";
-        case FieldDescriptorProto::TYPE_SFIXED64:
-            return "sfixed64";
-        case FieldDescriptorProto::TYPE_SINT32:
-            return "sint32";
-        case FieldDescriptorProto::TYPE_SINT64:
-            return "sint64";
-        default:
-            // won't happen
-            return "void";
-    }
-}
-
-static uint64_t
-get_field_id(const FieldDescriptorProto& field)
-{
-    // Number
-    uint64_t result = (uint32_t)field.number();
-
-    // Type
-    switch (field.type()) {
-        case FieldDescriptorProto::TYPE_DOUBLE:
-            result |= FIELD_TYPE_DOUBLE;
-            break;
-        case FieldDescriptorProto::TYPE_FLOAT:
-            result |= FIELD_TYPE_FLOAT;
-            break;
-        case FieldDescriptorProto::TYPE_INT64:
-            result |= FIELD_TYPE_INT64;
-            break;
-        case FieldDescriptorProto::TYPE_UINT64:
-            result |= FIELD_TYPE_UINT64;
-            break;
-        case FieldDescriptorProto::TYPE_INT32:
-            result |= FIELD_TYPE_INT32;
-            break;
-        case FieldDescriptorProto::TYPE_FIXED64:
-            result |= FIELD_TYPE_FIXED64;
-            break;
-        case FieldDescriptorProto::TYPE_FIXED32:
-            result |= FIELD_TYPE_FIXED32;
-            break;
-        case FieldDescriptorProto::TYPE_BOOL:
-            result |= FIELD_TYPE_BOOL;
-            break;
-        case FieldDescriptorProto::TYPE_STRING:
-            result |= FIELD_TYPE_STRING;
-            break;
-        case FieldDescriptorProto::TYPE_MESSAGE:
-            result |= FIELD_TYPE_OBJECT;
-            break;
-        case FieldDescriptorProto::TYPE_BYTES:
-            result |= FIELD_TYPE_BYTES;
-            break;
-        case FieldDescriptorProto::TYPE_UINT32:
-            result |= FIELD_TYPE_UINT32;
-            break;
-        case FieldDescriptorProto::TYPE_ENUM:
-            result |= FIELD_TYPE_ENUM;
-            break;
-        case FieldDescriptorProto::TYPE_SFIXED32:
-            result |= FIELD_TYPE_SFIXED32;
-            break;
-        case FieldDescriptorProto::TYPE_SFIXED64:
-            result |= FIELD_TYPE_SFIXED64;
-            break;
-        case FieldDescriptorProto::TYPE_SINT32:
-            result |= FIELD_TYPE_SINT32;
-            break;
-        case FieldDescriptorProto::TYPE_SINT64:
-            result |= FIELD_TYPE_SINT64;
-            break;
-        default:
-            ;
-    }
-
-    // Count
-    if (field.options().packed()) {
-        result |= FIELD_COUNT_PACKED;
-    } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
-        result |= FIELD_COUNT_REPEATED;
-    } else {
-        result |= FIELD_COUNT_SINGLE;
-    }
-
-    return result;
-}
-
-/**
  * Write a field.
  */
 static void
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
new file mode 100644
index 0000000..123506c
--- /dev/null
+++ b/tools/streaming_proto/stream.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "google/protobuf/descriptor.proto";
+
+package android.stream_proto;
+
+// This option tells streaming proto plugin to compile .proto files with extra features.
+message StreamFlags {
+  // creates a mapping of field names of the message to its field ids
+  optional bool enable_fields_mapping = 1;
+}
+
+extend google.protobuf.MessageOptions {
+    // Flags used by streaming proto plugins
+    optional StreamFlags stream = 126856794;
+}
diff --git a/tools/streaming_proto/stream_proto_utils.cpp b/tools/streaming_proto/stream_proto_utils.cpp
new file mode 100644
index 0000000..e8f86bc
--- /dev/null
+++ b/tools/streaming_proto/stream_proto_utils.cpp
@@ -0,0 +1,102 @@
+#include "stream_proto_utils.h"
+
+namespace android {
+namespace stream_proto {
+
+/**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5ULL << FIELD_COUNT_SHIFT;
+
+uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+    // Number
+    uint64_t result = (uint32_t)field.number();
+
+    // Type
+    result |= (uint64_t)field.type() << FIELD_TYPE_SHIFT;
+
+    // Count
+    if (field.options().packed()) {
+        result |= FIELD_COUNT_PACKED;
+    } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+        result |= FIELD_COUNT_REPEATED;
+    } else {
+        result |= FIELD_COUNT_SINGLE;
+    }
+
+    return result;
+}
+
+string
+get_proto_type(const FieldDescriptorProto& field)
+{
+    switch (field.type()) {
+        case FieldDescriptorProto::TYPE_DOUBLE:
+            return "double";
+        case FieldDescriptorProto::TYPE_FLOAT:
+            return "float";
+        case FieldDescriptorProto::TYPE_INT64:
+            return "int64";
+        case FieldDescriptorProto::TYPE_UINT64:
+            return "uint64";
+        case FieldDescriptorProto::TYPE_INT32:
+            return "int32";
+        case FieldDescriptorProto::TYPE_FIXED64:
+            return "fixed64";
+        case FieldDescriptorProto::TYPE_FIXED32:
+            return "fixed32";
+        case FieldDescriptorProto::TYPE_BOOL:
+            return "bool";
+        case FieldDescriptorProto::TYPE_STRING:
+            return "string";
+        case FieldDescriptorProto::TYPE_GROUP:
+            return "group<unsupported!>";
+        case FieldDescriptorProto::TYPE_MESSAGE:
+            return field.type_name();
+        case FieldDescriptorProto::TYPE_BYTES:
+            return "bytes";
+        case FieldDescriptorProto::TYPE_UINT32:
+            return "uint32";
+        case FieldDescriptorProto::TYPE_ENUM:
+            return field.type_name();
+        case FieldDescriptorProto::TYPE_SFIXED32:
+            return "sfixed32";
+        case FieldDescriptorProto::TYPE_SFIXED64:
+            return "sfixed64";
+        case FieldDescriptorProto::TYPE_SINT32:
+            return "sint32";
+        case FieldDescriptorProto::TYPE_SINT64:
+            return "sint64";
+        default:
+            // won't happen
+            return "void";
+    }
+}
+
+bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+    const int N = request.file_to_generate_size();
+    for (int i=0; i<N; i++) {
+        if (request.file_to_generate(i) == file) {
+            return true;
+        }
+    }
+    return false;
+}
+
+} // stream_proto
+} // android
diff --git a/tools/streaming_proto/stream_proto_utils.h b/tools/streaming_proto/stream_proto_utils.h
new file mode 100644
index 0000000..5297ecc
--- /dev/null
+++ b/tools/streaming_proto/stream_proto_utils.h
@@ -0,0 +1,29 @@
+#include <stdint.h>
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+
+namespace android {
+namespace stream_proto {
+
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace std;
+
+/**
+ * Get encoded field id from a field.
+ */
+uint64_t get_field_id(const FieldDescriptorProto& field);
+
+/**
+ * Get the string name for a field.
+ */
+string get_proto_type(const FieldDescriptorProto& field);
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+bool should_generate_for_file(const CodeGeneratorRequest& request, const string& file);
+
+} // stream_proto
+} // android
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
index 03284d1..d6f195f 100644
--- a/tools/streaming_proto/string_utils.h
+++ b/tools/streaming_proto/string_utils.h
@@ -6,6 +6,9 @@
 
 using namespace std;
 
+// Indent
+const string INDENT = "    ";
+
 /**
  * Capitalizes the string, removes underscores and makes the next letter
  * capitalized, and makes the letter following numbers capitalized.