Merge "Special handling of processes with recent tasks."
diff --git a/Android.bp b/Android.bp
index 24a9bca..2ea4894 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,7 @@
             // runtime, as well as the only protos that are actually
             // needed by the device.
             srcs: [
+                "core/proto/android/os/cpufreq.proto",
                 "core/proto/android/os/cpuinfo.proto",
                 "core/proto/android/os/kernelwake.proto",
                 "core/proto/android/os/pagetypeinfo.proto",
@@ -83,6 +84,7 @@
     ],
 
     srcs: [
+        "core/proto/android/os/cpufreq.proto",
         "core/proto/android/os/cpuinfo.proto",
         "core/proto/android/os/kernelwake.proto",
         "core/proto/android/os/pagetypeinfo.proto",
@@ -132,3 +134,29 @@
     dxflags: ["--core-library"],
     installable: false,
 }
+
+python_defaults {
+    name: "base_default",
+    version: {
+        py2: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+        py3: {
+            enabled: false,
+            embedded_launcher: false,
+        },
+    },
+}
+
+python_binary_host {
+    name: "fontchain_linter",
+    defaults: ["base_default"],
+    main: "tools/fonts/fontchain_linter.py",
+    srcs: [
+        "tools/fonts/fontchain_linter.py",
+    ],
+    libs: [
+        "fontTools",
+    ],
+}
diff --git a/Android.mk b/Android.mk
index 2e65074..08a8b64 100644
--- a/Android.mk
+++ b/Android.mk
@@ -169,7 +169,6 @@
 	core/java/android/content/pm/IPackageDataObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver2.aidl \
-	core/java/android/content/pm/IPackageInstallObserver.aidl \
 	core/java/android/content/pm/IPackageInstallObserver2.aidl \
 	core/java/android/content/pm/IPackageInstaller.aidl \
 	core/java/android/content/pm/IPackageInstallerCallback.aidl \
@@ -274,7 +273,6 @@
 	core/java/android/os/IRecoverySystemProgressListener.aidl \
 	core/java/android/os/IRemoteCallback.aidl \
 	core/java/android/os/ISchedulingPolicyService.aidl \
-	core/java/android/os/IStatsCallbacks.aidl \
 	core/java/android/os/IStatsCompanionService.aidl \
 	core/java/android/os/IStatsManager.aidl \
 	core/java/android/os/IThermalEventListener.aidl \
@@ -638,6 +636,7 @@
     framework-protos                                     \
     android.hidl.base-V1.0-java                          \
     android.hardware.cas-V1.0-java                       \
+    android.hardware.contexthub-V1.0-java                \
     android.hardware.health-V1.0-java-constants          \
     android.hardware.thermal-V1.0-java-constants         \
     android.hardware.tv.input-V1.0-java-constants        \
diff --git a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
index a92597f..6e4c9c5 100644
--- a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
@@ -27,6 +27,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class ParcelPerfTest {
@@ -167,4 +171,80 @@
             Parcel.obtain().recycle();
         }
     }
+
+    @Test
+    public void timeWriteException() {
+        timeWriteException(false);
+    }
+
+    @Test
+    public void timeWriteExceptionWithStackTraceParceling() {
+        timeWriteException(true);
+    }
+
+    @Test
+    public void timeReadException() {
+        timeReadException(false);
+    }
+
+    @Test
+    public void timeReadExceptionWithStackTraceParceling() {
+        timeReadException(true);
+    }
+
+    private void timeWriteException(boolean enableParceling) {
+        if (enableParceling) {
+            Parcel.setStackTraceParceling(true);
+        }
+        try {
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            Parcel p = Parcel.obtain();
+            SecurityException e = new SecurityException("TestMessage");
+            while (state.keepRunning()) {
+                p.setDataPosition(0);
+                p.writeException(e);
+            }
+        } finally {
+            if (enableParceling) {
+                Parcel.setStackTraceParceling(false);
+            }
+        }
+    }
+
+    private void timeReadException(boolean enableParceling) {
+        if (enableParceling) {
+            Parcel.setStackTraceParceling(true);
+        }
+        try {
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            Parcel p = Parcel.obtain();
+            String msg = "TestMessage";
+            p.writeException(new SecurityException(msg));
+            p.setDataPosition(0);
+            // First verify that remote cause is set (if parceling is enabled)
+            try {
+                p.readException();
+            } catch (SecurityException e) {
+                assertEquals(e.getMessage(), msg);
+                if (enableParceling) {
+                    assertTrue(e.getCause() instanceof RemoteException);
+                } else {
+                    assertNull(e.getCause());
+                }
+            }
+
+            while (state.keepRunning()) {
+                p.setDataPosition(0);
+                try {
+                    p.readException();
+                } catch (SecurityException expected) {
+                }
+            }
+        } finally {
+            if (enableParceling) {
+                Parcel.setStackTraceParceling(false);
+            }
+        }
+    }
+
 }
diff --git a/api/current.txt b/api/current.txt
index 06163cc..baf8e15 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2777,6 +2777,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+    field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
     field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
     field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -6973,11 +6974,12 @@
 
   public final class Slice implements android.os.Parcelable {
     ctor protected Slice(android.os.Parcel);
-    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
-    method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
+    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
     method public int describeContents();
     method public java.util.List<java.lang.String> getHints();
     method public java.util.List<android.app.slice.SliceItem> getItems();
+    method public android.app.slice.SliceSpec getSpec();
     method public android.net.Uri getUri();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
@@ -6986,55 +6988,59 @@
     field public static final java.lang.String HINT_LARGE = "large";
     field public static final java.lang.String HINT_LIST = "list";
     field public static final java.lang.String HINT_LIST_ITEM = "list_item";
-    field public static final java.lang.String HINT_MESSAGE = "message";
     field public static final java.lang.String HINT_NO_TINT = "no_tint";
     field public static final java.lang.String HINT_PARTIAL = "partial";
     field public static final java.lang.String HINT_SELECTED = "selected";
-    field public static final java.lang.String HINT_SOURCE = "source";
     field public static final java.lang.String HINT_TITLE = "title";
+    field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+    field public static final java.lang.String SUBTYPE_SOURCE = "source";
   }
 
   public static class Slice.Builder {
     ctor public Slice.Builder(android.net.Uri);
     ctor public Slice.Builder(android.app.slice.Slice.Builder);
     method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
-    method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
-    method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
     method public android.app.slice.Slice.Builder addHints(java.lang.String...);
     method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
-    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...);
     method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
-    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
-    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
-    method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>);
     method public android.app.slice.Slice build();
+    method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec);
   }
 
   public final class SliceItem implements android.os.Parcelable {
     method public int describeContents();
     method public android.app.PendingIntent getAction();
     method public int getColor();
+    method public java.lang.String getFormat();
     method public java.util.List<java.lang.String> getHints();
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput getRemoteInput();
     method public android.app.slice.Slice getSlice();
+    method public java.lang.String getSubType();
     method public java.lang.CharSequence getText();
     method public long getTimestamp();
-    method public int getType();
     method public boolean hasHint(java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
-    field public static final int TYPE_ACTION = 4; // 0x4
-    field public static final int TYPE_COLOR = 6; // 0x6
-    field public static final int TYPE_IMAGE = 3; // 0x3
-    field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
-    field public static final int TYPE_SLICE = 1; // 0x1
-    field public static final int TYPE_TEXT = 2; // 0x2
-    field public static final int TYPE_TIMESTAMP = 8; // 0x8
+    field public static final java.lang.String FORMAT_ACTION = "action";
+    field public static final java.lang.String FORMAT_COLOR = "color";
+    field public static final java.lang.String FORMAT_IMAGE = "image";
+    field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
+    field public static final java.lang.String FORMAT_SLICE = "slice";
+    field public static final java.lang.String FORMAT_TEXT = "text";
+    field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp";
   }
 
   public abstract class SliceProvider extends android.content.ContentProvider {
@@ -7042,7 +7048,8 @@
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
+    method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri);
     method public android.net.Uri onMapIntentToUri(android.content.Intent);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
@@ -7051,25 +7058,14 @@
     field public static final java.lang.String SLICE_TYPE = "vnd.android.slice";
   }
 
-}
-
-package android.app.slice.widget {
-
-  public class SliceView extends android.view.ViewGroup {
-    ctor public SliceView(android.content.Context);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
-    method public void clearSlice();
-    method public java.lang.String getMode();
-    method protected void onLayout(boolean, int, int, int, int);
-    method public void setMode(java.lang.String);
-    method public void setScrollable(boolean);
-    method public boolean setSlice(android.net.Uri);
-    method public void showSlice(android.app.slice.Slice);
-    field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
-    field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
-    field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+  public final class SliceSpec implements android.os.Parcelable {
+    ctor public SliceSpec(java.lang.String, int);
+    method public boolean canRender(android.app.slice.SliceSpec);
+    method public int describeContents();
+    method public int getRevision();
+    method public java.lang.String getType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
   }
 
 }
@@ -38624,7 +38620,6 @@
     method public static void remove(java.lang.String) throws android.system.ErrnoException;
     method public static void removexattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
     method public static void rename(java.lang.String, java.lang.String) throws android.system.ErrnoException;
-    method public static deprecated long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.util.MutableLong, long) throws android.system.ErrnoException;
     method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.system.Int64Ref, long) throws android.system.ErrnoException;
     method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
     method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
@@ -38650,7 +38645,6 @@
     method public static int umask(int);
     method public static android.system.StructUtsname uname();
     method public static void unsetenv(java.lang.String) throws android.system.ErrnoException;
-    method public static deprecated int waitpid(int, android.util.MutableInt, int) throws android.system.ErrnoException;
     method public static int waitpid(int, android.system.Int32Ref, int) throws android.system.ErrnoException;
     method public static int write(java.io.FileDescriptor, java.nio.ByteBuffer) throws android.system.ErrnoException, java.io.InterruptedIOException;
     method public static int write(java.io.FileDescriptor, byte[], int, int) throws android.system.ErrnoException, java.io.InterruptedIOException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 808f222..a9afdd0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2919,6 +2919,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+    field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
     field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
     field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -7417,11 +7418,12 @@
 
   public final class Slice implements android.os.Parcelable {
     ctor protected Slice(android.os.Parcel);
-    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
-    method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
+    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
     method public int describeContents();
     method public java.util.List<java.lang.String> getHints();
     method public java.util.List<android.app.slice.SliceItem> getItems();
+    method public android.app.slice.SliceSpec getSpec();
     method public android.net.Uri getUri();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
@@ -7430,55 +7432,59 @@
     field public static final java.lang.String HINT_LARGE = "large";
     field public static final java.lang.String HINT_LIST = "list";
     field public static final java.lang.String HINT_LIST_ITEM = "list_item";
-    field public static final java.lang.String HINT_MESSAGE = "message";
     field public static final java.lang.String HINT_NO_TINT = "no_tint";
     field public static final java.lang.String HINT_PARTIAL = "partial";
     field public static final java.lang.String HINT_SELECTED = "selected";
-    field public static final java.lang.String HINT_SOURCE = "source";
     field public static final java.lang.String HINT_TITLE = "title";
+    field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+    field public static final java.lang.String SUBTYPE_SOURCE = "source";
   }
 
   public static class Slice.Builder {
     ctor public Slice.Builder(android.net.Uri);
     ctor public Slice.Builder(android.app.slice.Slice.Builder);
     method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
-    method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
-    method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
     method public android.app.slice.Slice.Builder addHints(java.lang.String...);
     method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
-    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...);
     method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
-    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
-    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
-    method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>);
     method public android.app.slice.Slice build();
+    method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec);
   }
 
   public final class SliceItem implements android.os.Parcelable {
     method public int describeContents();
     method public android.app.PendingIntent getAction();
     method public int getColor();
+    method public java.lang.String getFormat();
     method public java.util.List<java.lang.String> getHints();
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput getRemoteInput();
     method public android.app.slice.Slice getSlice();
+    method public java.lang.String getSubType();
     method public java.lang.CharSequence getText();
     method public long getTimestamp();
-    method public int getType();
     method public boolean hasHint(java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
-    field public static final int TYPE_ACTION = 4; // 0x4
-    field public static final int TYPE_COLOR = 6; // 0x6
-    field public static final int TYPE_IMAGE = 3; // 0x3
-    field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
-    field public static final int TYPE_SLICE = 1; // 0x1
-    field public static final int TYPE_TEXT = 2; // 0x2
-    field public static final int TYPE_TIMESTAMP = 8; // 0x8
+    field public static final java.lang.String FORMAT_ACTION = "action";
+    field public static final java.lang.String FORMAT_COLOR = "color";
+    field public static final java.lang.String FORMAT_IMAGE = "image";
+    field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
+    field public static final java.lang.String FORMAT_SLICE = "slice";
+    field public static final java.lang.String FORMAT_TEXT = "text";
+    field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp";
   }
 
   public abstract class SliceProvider extends android.content.ContentProvider {
@@ -7486,7 +7492,8 @@
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
+    method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri);
     method public android.net.Uri onMapIntentToUri(android.content.Intent);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
@@ -7495,25 +7502,14 @@
     field public static final java.lang.String SLICE_TYPE = "vnd.android.slice";
   }
 
-}
-
-package android.app.slice.widget {
-
-  public class SliceView extends android.view.ViewGroup {
-    ctor public SliceView(android.content.Context);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
-    method public void clearSlice();
-    method public java.lang.String getMode();
-    method protected void onLayout(boolean, int, int, int, int);
-    method public void setMode(java.lang.String);
-    method public void setScrollable(boolean);
-    method public boolean setSlice(android.net.Uri);
-    method public void showSlice(android.app.slice.Slice);
-    field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
-    field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
-    field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+  public final class SliceSpec implements android.os.Parcelable {
+    ctor public SliceSpec(java.lang.String, int);
+    method public boolean canRender(android.app.slice.SliceSpec);
+    method public int describeContents();
+    method public int getRevision();
+    method public java.lang.String getType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
   }
 
 }
@@ -41928,7 +41924,6 @@
     method public static void remove(java.lang.String) throws android.system.ErrnoException;
     method public static void removexattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
     method public static void rename(java.lang.String, java.lang.String) throws android.system.ErrnoException;
-    method public static deprecated long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.util.MutableLong, long) throws android.system.ErrnoException;
     method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.system.Int64Ref, long) throws android.system.ErrnoException;
     method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
     method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
@@ -41954,7 +41949,6 @@
     method public static int umask(int);
     method public static android.system.StructUtsname uname();
     method public static void unsetenv(java.lang.String) throws android.system.ErrnoException;
-    method public static deprecated int waitpid(int, android.util.MutableInt, int) throws android.system.ErrnoException;
     method public static int waitpid(int, android.system.Int32Ref, int) throws android.system.ErrnoException;
     method public static int write(java.io.FileDescriptor, java.nio.ByteBuffer) throws android.system.ErrnoException, java.io.InterruptedIOException;
     method public static int write(java.io.FileDescriptor, byte[], int, int) throws android.system.ErrnoException, java.io.InterruptedIOException;
@@ -48196,8 +48190,8 @@
   }
 
   public final class StatsManager {
-    method public byte[] getData(java.lang.String);
     method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
+    method public byte[] getData(java.lang.String);
     method public boolean removeConfiguration(java.lang.String);
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index da59056..4b49c83 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2777,6 +2777,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+    field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
     field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
     field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -7047,11 +7048,12 @@
 
   public final class Slice implements android.os.Parcelable {
     ctor protected Slice(android.os.Parcel);
-    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
-    method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
+    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
     method public int describeContents();
     method public java.util.List<java.lang.String> getHints();
     method public java.util.List<android.app.slice.SliceItem> getItems();
+    method public android.app.slice.SliceSpec getSpec();
     method public android.net.Uri getUri();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
@@ -7060,55 +7062,59 @@
     field public static final java.lang.String HINT_LARGE = "large";
     field public static final java.lang.String HINT_LIST = "list";
     field public static final java.lang.String HINT_LIST_ITEM = "list_item";
-    field public static final java.lang.String HINT_MESSAGE = "message";
     field public static final java.lang.String HINT_NO_TINT = "no_tint";
     field public static final java.lang.String HINT_PARTIAL = "partial";
     field public static final java.lang.String HINT_SELECTED = "selected";
-    field public static final java.lang.String HINT_SOURCE = "source";
     field public static final java.lang.String HINT_TITLE = "title";
+    field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+    field public static final java.lang.String SUBTYPE_SOURCE = "source";
   }
 
   public static class Slice.Builder {
     ctor public Slice.Builder(android.net.Uri);
     ctor public Slice.Builder(android.app.slice.Slice.Builder);
     method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
-    method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
-    method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
     method public android.app.slice.Slice.Builder addHints(java.lang.String...);
     method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
-    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String, java.lang.String...);
     method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
-    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
-    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
-    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
-    method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice, java.lang.String);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.lang.String...);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String, java.util.List<java.lang.String>);
     method public android.app.slice.Slice build();
+    method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec);
   }
 
   public final class SliceItem implements android.os.Parcelable {
     method public int describeContents();
     method public android.app.PendingIntent getAction();
     method public int getColor();
+    method public java.lang.String getFormat();
     method public java.util.List<java.lang.String> getHints();
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput getRemoteInput();
     method public android.app.slice.Slice getSlice();
+    method public java.lang.String getSubType();
     method public java.lang.CharSequence getText();
     method public long getTimestamp();
-    method public int getType();
     method public boolean hasHint(java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
-    field public static final int TYPE_ACTION = 4; // 0x4
-    field public static final int TYPE_COLOR = 6; // 0x6
-    field public static final int TYPE_IMAGE = 3; // 0x3
-    field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
-    field public static final int TYPE_SLICE = 1; // 0x1
-    field public static final int TYPE_TEXT = 2; // 0x2
-    field public static final int TYPE_TIMESTAMP = 8; // 0x8
+    field public static final java.lang.String FORMAT_ACTION = "action";
+    field public static final java.lang.String FORMAT_COLOR = "color";
+    field public static final java.lang.String FORMAT_IMAGE = "image";
+    field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
+    field public static final java.lang.String FORMAT_SLICE = "slice";
+    field public static final java.lang.String FORMAT_TEXT = "text";
+    field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp";
   }
 
   public abstract class SliceProvider extends android.content.ContentProvider {
@@ -7116,7 +7122,8 @@
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
+    method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri);
     method public android.net.Uri onMapIntentToUri(android.content.Intent);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
@@ -7125,25 +7132,14 @@
     field public static final java.lang.String SLICE_TYPE = "vnd.android.slice";
   }
 
-}
-
-package android.app.slice.widget {
-
-  public class SliceView extends android.view.ViewGroup {
-    ctor public SliceView(android.content.Context);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
-    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
-    method public void clearSlice();
-    method public java.lang.String getMode();
-    method protected void onLayout(boolean, int, int, int, int);
-    method public void setMode(java.lang.String);
-    method public void setScrollable(boolean);
-    method public boolean setSlice(android.net.Uri);
-    method public void showSlice(android.app.slice.Slice);
-    field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
-    field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
-    field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+  public final class SliceSpec implements android.os.Parcelable {
+    ctor public SliceSpec(java.lang.String, int);
+    method public boolean canRender(android.app.slice.SliceSpec);
+    method public int describeContents();
+    method public int getRevision();
+    method public java.lang.String getType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
   }
 
 }
@@ -39015,7 +39011,6 @@
     method public static void remove(java.lang.String) throws android.system.ErrnoException;
     method public static void removexattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
     method public static void rename(java.lang.String, java.lang.String) throws android.system.ErrnoException;
-    method public static deprecated long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.util.MutableLong, long) throws android.system.ErrnoException;
     method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.system.Int64Ref, long) throws android.system.ErrnoException;
     method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
     method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
@@ -39041,7 +39036,6 @@
     method public static int umask(int);
     method public static android.system.StructUtsname uname();
     method public static void unsetenv(java.lang.String) throws android.system.ErrnoException;
-    method public static deprecated int waitpid(int, android.util.MutableInt, int) throws android.system.ErrnoException;
     method public static int waitpid(int, android.system.Int32Ref, int) throws android.system.ErrnoException;
     method public static int write(java.io.FileDescriptor, java.nio.ByteBuffer) throws android.system.ErrnoException, java.io.InterruptedIOException;
     method public static int write(java.io.FileDescriptor, byte[], int, int) throws android.system.ErrnoException, java.io.InterruptedIOException;
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index 8239d8e..c8a0883 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "incident_helper"
 
+#include "parsers/CpuFreqParser.h"
 #include "parsers/CpuInfoParser.h"
 #include "parsers/KernelWakesParser.h"
 #include "parsers/PageTypeInfoParser.h"
@@ -60,6 +61,8 @@
             return new KernelWakesParser();
         case 2003:
             return new CpuInfoParser();
+        case 2004:
+            return new CpuFreqParser();
         default:
             return NULL;
     }
diff --git a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
new file mode 100644
index 0000000..02f1ce7
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 <unistd.h>
+
+#include "frameworks/base/core/proto/android/os/cpufreq.proto.h"
+#include "ih_util.h"
+#include "CpuFreqParser.h"
+
+using namespace android::os;
+
+status_t
+CpuFreqParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+
+    // parse header
+    reader.readLine(&line);
+    header_t header = parseHeader(line, TAB_DELIMITER);
+    if (header.size() < 1) {
+        fprintf(stderr, "Bad header: %s\n", line.c_str());
+        return BAD_VALUE;
+    }
+    const int numCpus = (int)header.size() - 1;
+    vector<pair<int, long long>> cpucores[numCpus];
+
+    // parse freq and time
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        record_t record = parseRecord(line, TAB_DELIMITER);
+        if (record.size() != header.size()) {
+            fprintf(stderr, "Bad line: %s\n", line.c_str());
+            continue;
+        }
+
+        int freq = toInt(record[0]);
+        for (int i=0; i<numCpus; i++) {
+            if (strcmp(record[i+1].c_str(), "N/A") == 0) {
+                continue;
+            }
+            cpucores[i].push_back(make_pair(freq, toLongLong(record[i+1])));
+        }
+    }
+
+    ProtoOutputStream proto;
+
+    long jiffyHz = sysconf(_SC_CLK_TCK);
+    proto.write(CpuFreq::JIFFY_HZ, (int)jiffyHz);
+
+    for (int i=0; i<numCpus; i++) {
+        long long token = proto.start(CpuFreq::CPU_FREQS);
+        proto.write(CpuFreqStats::CPU_NAME, header[i+1]);
+        for (vector<pair<int, long long>>::iterator it = cpucores[i].begin(); it != cpucores[i].end(); it++) {
+            long long stateToken = proto.start(CpuFreqStats::TIMES);
+            proto.write(CpuFreqStats::TimeInState::STATE_KHZ, it->first);
+            proto.write(CpuFreqStats::TimeInState::TIME_JIFFY, it->second);
+            proto.end(stateToken);
+        }
+        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/CpuFreqParser.h b/cmds/incident_helper/src/parsers/CpuFreqParser.h
new file mode 100644
index 0000000..470d568
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuFreqParser.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 CPU_FREQ_PARSER_H
+#define CPU_FREQ_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Cpu frequency parser, parses text in /sys/devices/system/cpu/cpufreq/all_time_in_state
+ */
+class CpuFreqParser : public TextParserBase {
+public:
+    CpuFreqParser() : TextParserBase(String8("CpuFreqParser")) {};
+    ~CpuFreqParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // CPU_FREQ_PARSER_H
diff --git a/cmds/incident_helper/testdata/cpufreq.txt b/cmds/incident_helper/testdata/cpufreq.txt
new file mode 100644
index 0000000..6472839
--- /dev/null
+++ b/cmds/incident_helper/testdata/cpufreq.txt
@@ -0,0 +1,6 @@
+freq		cpu0		cpu1		cpu2		cpu3		
+307200		23860761		23860761		23890935		23890935		
+384000		83124		83124		29383		29383		
+748800		N/A		N/A		10547		10547		
+768000		22652		22652		N/A		N/A		
+825600		N/A		N/A		13173		13173			
diff --git a/cmds/incident_helper/tests/CpuFreqParser_test.cpp b/cmds/incident_helper/tests/CpuFreqParser_test.cpp
new file mode 100644
index 0000000..1c2f9e5
--- /dev/null
+++ b/cmds/incident_helper/tests/CpuFreqParser_test.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "CpuFreqParser.h"
+
+#include "frameworks/base/core/proto/android/os/cpufreq.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 CpuFreqParserTest : 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(CpuFreqParserTest, Success) {
+    const string testFile = kTestDataPath + "cpufreq.txt";
+    CpuFreqParser parser;
+    CpuFreq expected;
+
+    long jiffyHz = sysconf(_SC_CLK_TCK);
+    expected.set_jiffy_hz(jiffyHz);
+
+    CpuFreqStats::TimeInState* state;
+
+    CpuFreqStats* cpu0 = expected.add_cpu_freqs();
+    cpu0->set_cpu_name("cpu0");
+    state = cpu0->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23860761);
+    state = cpu0->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(83124);
+    state = cpu0->add_times();
+    state->set_state_khz(768000);
+    state->set_time_jiffy(22652);
+
+    CpuFreqStats* cpu1 = expected.add_cpu_freqs();
+    cpu1->set_cpu_name("cpu1");
+    state = cpu1->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23860761);
+    state = cpu1->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(83124);
+    state = cpu1->add_times();
+    state->set_state_khz(768000);
+    state->set_time_jiffy(22652);
+
+    CpuFreqStats* cpu2 = expected.add_cpu_freqs();
+    cpu2->set_cpu_name("cpu2");
+    state = cpu2->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23890935);
+    state = cpu2->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(29383);
+    state = cpu2->add_times();
+    state->set_state_khz(748800);
+    state->set_time_jiffy(10547);
+    state = cpu2->add_times();
+    state->set_state_khz(825600);
+    state->set_time_jiffy(13173);
+
+    CpuFreqStats* cpu3 = expected.add_cpu_freqs();
+    cpu3->set_cpu_name("cpu3");
+    state = cpu3->add_times();
+    state->set_state_khz(307200);
+    state->set_time_jiffy(23890935);
+    state = cpu3->add_times();
+    state->set_state_khz(384000);
+    state->set_time_jiffy(29383);
+    state = cpu3->add_times();
+    state->set_state_khz(748800);
+    state->set_time_jiffy(10547);
+    state = cpu3->add_times();
+    state->set_state_khz(825600);
+    state->set_time_jiffy(13173);
+
+    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/CpuInfoParser_test.cpp b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
index 57ad15c..bbc14bc 100644
--- a/cmds/incident_helper/tests/CpuInfoParser_test.cpp
+++ b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
@@ -56,7 +56,7 @@
     const string kTestDataPath = kTestPath + "/testdata/";
 };
 
-TEST_F(CpuInfoParserTest, HasSwapInfo) {
+TEST_F(CpuInfoParserTest, Success) {
     const string testFile = kTestDataPath + "cpuinfo.txt";
     CpuInfoParser parser;
     CpuInfo expected;
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index b7633a4..30dd339 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -26,6 +26,7 @@
 #include <unistd.h>
 #include <wait.h>
 
+const bool DEBUG = false;
 const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB
 const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max
 
@@ -71,9 +72,11 @@
             mTimedOut = true;
             break;
         } else if (count < 0) {
+            if (DEBUG) ALOGD("poll failed: %s", strerror(errno));
             return -errno;
         } else {
             if ((pfds.revents & POLLERR) != 0) {
+                if (DEBUG) ALOGD("return event has error %s", strerror(errno));
                 return errno != 0 ? -errno : UNKNOWN_ERROR;
             } else {
                 ssize_t amt = ::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite());
@@ -81,6 +84,7 @@
                     if (errno == EAGAIN || errno == EWOULDBLOCK) {
                         continue;
                     } else {
+                        if (DEBUG) ALOGD("Fail to read %d: %s", fd, strerror(errno));
                         return -errno;
                     }
                 } else if (amt == 0) {
@@ -95,7 +99,7 @@
 }
 
 status_t
-FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs)
+FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs, const bool isSysfs)
 {
     struct pollfd pfds[] = {
         { .fd = fd,     .events = POLLIN  },
@@ -135,12 +139,18 @@
             mTimedOut = true;
             break;
         } else if (count < 0) {
+            if (DEBUG) ALOGD("Fail to poll: %s", strerror(errno));
             return -errno;
         }
 
         // make sure no errors occur on any fds
         for (int i = 0; i < 3; ++i) {
             if ((pfds[i].revents & POLLERR) != 0) {
+                if (i == 0 && isSysfs) {
+                    if (DEBUG) ALOGD("fd %d is sysfs, ignore its POLLERR return value", fd);
+                    continue;
+                }
+                if (DEBUG) ALOGD("fd[%d]=%d returns error events: %s", i, fd, strerror(errno));
                 return errno != 0 ? -errno : UNKNOWN_ERROR;
             }
         }
@@ -155,6 +165,7 @@
             }
             if (amt < 0) {
                 if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+                    if (DEBUG) ALOGD("Fail to read fd %d: %s", fd, strerror(errno));
                     return -errno;
                 } // otherwise just continue
             } else if (amt == 0) {  // reach EOF so don't have to poll pfds[0].
@@ -176,6 +187,7 @@
             }
             if (amt < 0) {
                 if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+                    if (DEBUG) ALOGD("Fail to write toFd %d: %s", toFd, strerror(errno));
                     return -errno;
                 } // otherwise just continue
             } else {
@@ -202,6 +214,7 @@
         ssize_t amt = ::read(fromFd, mBuffer.writeBuffer(), mBuffer.currentToWrite());
         if (amt < 0) {
             if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+                if (DEBUG) ALOGD("Fail to read fromFd %d: %s", fromFd, strerror(errno));
                 return -errno;
             } // otherwise just continue
         } else if (amt == 0) {
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 8857ae7..48dc855 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -47,8 +47,10 @@
      * and stores the processed data from 'fromFd' in memory for later usage.
      * This function behaves in a streaming fashion in order to save memory usage.
      * Returns NO_ERROR if there were no errors or if we timed out.
+     *
+     * Poll will return POLLERR if fd is from sysfs, handle this edge case.
      */
-    status_t readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs);
+    status_t readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs, const bool isSysfs=false);
 
     /**
      * Whether we timed out.
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 892bcca..c08b9ea 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -232,6 +232,7 @@
      mFilename(filename)
 {
     name = filename;
+    mIsSysfs = strncmp(filename, "/sys/", 5) == 0;
 }
 
 FileSection::~FileSection() {}
@@ -264,7 +265,7 @@
 
     // parent process
     status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
-            this->timeoutMs);
+            this->timeoutMs, mIsSysfs);
     if (readStatus != NO_ERROR || buffer.timedOut()) {
         ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s",
             this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 0a1e03e..64558a6 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -69,6 +69,7 @@
 
 private:
     const char* mFilename;
+    bool mIsSysfs; // sysfs files are pollable but return POLLERR by default, handle it separately
 };
 
 /**
diff --git a/cmds/pm/Android.mk b/cmds/pm/Android.mk
index 6a03def..960c805 100644
--- a/cmds/pm/Android.mk
+++ b/cmds/pm/Android.mk
@@ -3,14 +3,8 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := pmlib
-LOCAL_MODULE_STEM := pm
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := pm
-LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_SRC_FILES := pm
-LOCAL_REQUIRED_MODULES := pmlib
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
 include $(BUILD_PREBUILT)
diff --git a/cmds/pm/pm b/cmds/pm/pm
index 53f85b2..4d1f945 100755
--- a/cmds/pm/pm
+++ b/cmds/pm/pm
@@ -1,8 +1,2 @@
 #!/system/bin/sh
-# Script to start "pm" on the device, which has a very rudimentary
-# shell.
-#
-base=/system
-export CLASSPATH=$base/framework/pm.jar
-exec app_process $base/bin com.android.commands.pm.Pm "$@"
-
+cmd package "$@"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880..0000000
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * Copyright (C) 2007 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.commands.pm;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-
-import android.accounts.IAccountManager;
-import android.app.ActivityManager;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-public final class Pm {
-    private static final String TAG = "Pm";
-    private static final String STDIN_PATH = "-";
-
-    IPackageManager mPm;
-    IPackageInstaller mInstaller;
-    IUserManager mUm;
-    IAccountManager mAm;
-
-    private String[] mArgs;
-    private int mNextArg;
-    private String mCurArgData;
-
-    private static final String PM_NOT_RUNNING_ERR =
-        "Error: Could not access the Package Manager.  Is the system running?";
-
-    public static void main(String[] args) {
-        int exitCode = 1;
-        try {
-            exitCode = new Pm().run(args);
-        } catch (Exception e) {
-            Log.e(TAG, "Error", e);
-            System.err.println("Error: " + e);
-            if (e instanceof RemoteException) {
-                System.err.println(PM_NOT_RUNNING_ERR);
-            }
-        }
-        System.exit(exitCode);
-    }
-
-    public int run(String[] args) throws RemoteException {
-        if (args.length < 1) {
-            return runShellCommand("package", mArgs);
-        }
-        mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
-        mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
-        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-
-        if (mPm == null) {
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-        mInstaller = mPm.getPackageInstaller();
-
-        mArgs = args;
-        String op = args[0];
-        mNextArg = 1;
-
-        if ("install".equals(op)) {
-            return runInstall();
-        }
-
-        if ("install-create".equals(op)) {
-            return runInstallCreate();
-        }
-
-        if ("install-write".equals(op)) {
-            return runInstallWrite();
-        }
-
-        if ("install-commit".equals(op)) {
-            return runInstallCommit();
-        }
-
-        if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
-            return runInstallAbandon();
-        }
-
-        return runShellCommand("package", mArgs);
-    }
-
-    static final class MyShellCallback extends ShellCallback {
-        @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
-                String mode) {
-            File file = new File(path);
-            final ParcelFileDescriptor fd;
-            try {
-                fd = ParcelFileDescriptor.open(file,
-                            ParcelFileDescriptor.MODE_CREATE |
-                            ParcelFileDescriptor.MODE_TRUNCATE |
-                            ParcelFileDescriptor.MODE_WRITE_ONLY);
-            } catch (FileNotFoundException e) {
-                String msg = "Unable to open file " + path + ": " + e;
-                System.err.println(msg);
-                throw new IllegalArgumentException(msg);
-            }
-            if (seLinuxContext != null) {
-                final String tcon = SELinux.getFileContext(file.getAbsolutePath());
-                if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
-                    try {
-                        fd.close();
-                    } catch (IOException e) {
-                    }
-                    String msg = "System server has no access to file context " + tcon;
-                    System.err.println(msg + " (from path " + file.getAbsolutePath()
-                            + ", context " + seLinuxContext + ")");
-                    throw new IllegalArgumentException(msg);
-                }
-            }
-            return fd;
-        }
-    }
-
-    private int runShellCommand(String serviceName, String[] args) {
-        final HandlerThread handlerThread = new HandlerThread("results");
-        handlerThread.start();
-        try {
-            ServiceManager.getService(serviceName).shellCommand(
-                    FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                    args, new MyShellCallback(),
-                    new ResultReceiver(new Handler(handlerThread.getLooper())));
-            return 0;
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        } finally {
-            handlerThread.quitSafely();
-        }
-        return -1;
-    }
-
-    private static class LocalIntentReceiver {
-        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    private int translateUserId(int userId, String logContext) {
-        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, logContext, "pm command");
-    }
-
-    private static String checkAbiArgument(String abi) {
-        if (TextUtils.isEmpty(abi)) {
-            throw new IllegalArgumentException("Missing ABI argument");
-        }
-        if ("-".equals(abi)) {
-            return abi;
-        }
-        final String[] supportedAbis = Build.SUPPORTED_ABIS;
-        for (String supportedAbi : supportedAbis) {
-            if (supportedAbi.equals(abi)) {
-                return abi;
-            }
-        }
-        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
-    }
-
-    /*
-     * Keep this around to support existing users of the "pm install" command that may not be
-     * able to be updated [or, at least informed the API has changed] such as ddmlib.
-     *
-     * Moving the implementation of "pm install" to "cmd package install" changes the executing
-     * context. Instead of being a stand alone process, "cmd package install" runs in the
-     * system_server process. Due to SELinux rules, system_server cannot access many directories;
-     * one of which being the package install staging directory [/data/local/tmp].
-     *
-     * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
-     */
-    private int runInstall() throws RemoteException {
-        long startedTime = SystemClock.elapsedRealtime();
-        final InstallParams params = makeInstallParams();
-        final String inPath = nextArg();
-        if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
-            File file = new File(inPath);
-            if (file.isFile()) {
-                try {
-                    ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
-                            null, null);
-                    params.sessionParams.setSize(
-                            PackageHelper.calculateInstalledSize(pkgLite,
-                            params.sessionParams.abiOverride));
-                } catch (PackageParserException | IOException e) {
-                    System.err.println("Error: Failed to parse APK file: " + e);
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: Can't open non-file: " + inPath);
-                return 1;
-            }
-        }
-
-        final int sessionId = doCreateSession(params.sessionParams,
-                params.installerPackageName, params.userId);
-
-        try {
-            if (inPath == null && params.sessionParams.sizeBytes == -1) {
-                System.err.println("Error: must either specify a package size or an APK file");
-                return 1;
-            }
-            if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
-                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
-            if (status.second != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
-                    - startedTime) + " ms");
-            System.out.println("Success");
-            return 0;
-        } finally {
-            try {
-                mInstaller.abandonSession(sessionId);
-            } catch (Exception ignore) {
-            }
-        }
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(nextArg());
-        return doAbandonSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(nextArg());
-        return doCommitSession(sessionId, true /*logSuccess*/).second;
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        final InstallParams installParams = makeInstallParams();
-        final int sessionId = doCreateSession(installParams.sessionParams,
-                installParams.installerPackageName, installParams.userId);
-
-        // NOTE: adb depends on parsing this string
-        System.out.println("Success: created install session [" + sessionId + "]");
-        return PackageInstaller.STATUS_SUCCESS;
-    }
-
-    private int runInstallWrite() throws RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(nextArg());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(nextArg());
-        final String splitName = nextArg();
-        final String path = nextArg();
-        return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
-    }
-
-    private static class InstallParams {
-        SessionParams sessionParams;
-        String installerPackageName;
-        int userId = UserHandle.USER_ALL;
-    }
-
-    private InstallParams makeInstallParams() {
-        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
-        final InstallParams params = new InstallParams();
-        params.sessionParams = sessionParams;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            switch (opt) {
-                case "-l":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-                    break;
-                case "-r":
-                    sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-                    break;
-                case "-i":
-                    params.installerPackageName = nextArg();
-                    if (params.installerPackageName == null) {
-                        throw new IllegalArgumentException("Missing installer package");
-                    }
-                    break;
-                case "-t":
-                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
-                    break;
-                case "-s":
-                    sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
-                    break;
-                case "-f":
-                    sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
-                    break;
-                case "-d":
-                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
-                    break;
-                case "-g":
-                    sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
-                    break;
-                case "--dont-kill":
-                    sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
-                    break;
-                case "--originating-uri":
-                    sessionParams.originatingUri = Uri.parse(nextOptionData());
-                    break;
-                case "--referrer":
-                    sessionParams.referrerUri = Uri.parse(nextOptionData());
-                    break;
-                case "-p":
-                    sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
-                    sessionParams.appPackageName = nextOptionData();
-                    if (sessionParams.appPackageName == null) {
-                        throw new IllegalArgumentException("Missing inherit package name");
-                    }
-                    break;
-                case "--pkg":
-                    sessionParams.appPackageName = nextOptionData();
-                    if (sessionParams.appPackageName == null) {
-                        throw new IllegalArgumentException("Missing package name");
-                    }
-                    break;
-                case "-S":
-                    final long sizeBytes = Long.parseLong(nextOptionData());
-                    if (sizeBytes <= 0) {
-                        throw new IllegalArgumentException("Size must be positive");
-                    }
-                    sessionParams.setSize(sizeBytes);
-                    break;
-                case "--abi":
-                    sessionParams.abiOverride = checkAbiArgument(nextOptionData());
-                    break;
-                case "--ephemeral":
-                case "--instant":
-                    sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
-                    break;
-                case "--full":
-                    sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
-                    break;
-                case "--user":
-                    params.userId = UserHandle.parseUserArg(nextOptionData());
-                    break;
-                case "--install-location":
-                    sessionParams.installLocation = Integer.parseInt(nextOptionData());
-                    break;
-                case "--force-uuid":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
-                    sessionParams.volumeUuid = nextOptionData();
-                    if ("internal".equals(sessionParams.volumeUuid)) {
-                        sessionParams.volumeUuid = null;
-                    }
-                    break;
-                case "--force-sdk":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown option " + opt);
-            }
-        }
-        return params;
-    }
-
-    private int doCreateSession(SessionParams params, String installerPackageName, int userId)
-            throws RemoteException {
-        userId = translateUserId(userId, "runInstallCreate");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
-        }
-
-        final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
-        return sessionId;
-    }
-
-    private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
-            boolean logSuccess) throws RemoteException {
-        if (STDIN_PATH.equals(inPath)) {
-            inPath = null;
-        } else if (inPath != null) {
-            final File file = new File(inPath);
-            if (file.isFile()) {
-                sizeBytes = file.length();
-            }
-        }
-
-        final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
-        PackageInstaller.Session session = null;
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInstaller.openSession(sessionId));
-
-            if (inPath != null) {
-                in = new FileInputStream(inPath);
-            } else {
-                in = new SizedInputStream(System.in, sizeBytes);
-            }
-            out = session.openWrite(splitName, 0, sizeBytes);
-
-            int total = 0;
-            byte[] buffer = new byte[1024 * 1024];
-            int c;
-            while ((c = in.read(buffer)) != -1) {
-                total += c;
-                out.write(buffer, 0, c);
-
-                if (info.sizeBytes > 0) {
-                    final float fraction = ((float) c / (float) info.sizeBytes);
-                    session.addProgress(fraction);
-                }
-            }
-            session.fsync(out);
-
-            if (logSuccess) {
-                System.out.println("Success: streamed " + total + " bytes");
-            }
-            return PackageInstaller.STATUS_SUCCESS;
-        } catch (IOException e) {
-            System.err.println("Error: failed to write; " + e.getMessage());
-            return PackageInstaller.STATUS_FAILURE;
-        } finally {
-            IoUtils.closeQuietly(out);
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
-            throws RemoteException {
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInstaller.openSession(sessionId));
-
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
-            session.commit(receiver.getIntentSender());
-
-            final Intent result = receiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == PackageInstaller.STATUS_SUCCESS) {
-                if (logSuccess) {
-                    System.out.println("Success");
-                }
-            } else {
-                System.err.println("Failure ["
-                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            }
-            return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
-            session.abandon();
-            if (logSuccess) {
-                System.out.println("Success");
-            }
-            return PackageInstaller.STATUS_SUCCESS;
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    class LocalPackageInstallObserver extends PackageInstallObserver {
-        boolean finished;
-        int result;
-        String extraPermission;
-        String extraPackage;
-
-        @Override
-        public void onPackageInstalled(String name, int status, String msg, Bundle extras) {
-            synchronized (this) {
-                finished = true;
-                result = status;
-                if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) {
-                    extraPermission = extras.getString(
-                            PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION);
-                    extraPackage = extras.getString(
-                            PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
-                }
-                notifyAll();
-            }
-        }
-    }
-
-    private static boolean isNumber(String s) {
-        try {
-            Integer.parseInt(s);
-        } catch (NumberFormatException nfe) {
-            return false;
-        }
-        return true;
-    }
-
-    static class ClearCacheObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-
-    }
-
-    static class ClearDataObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Displays the package file for a package.
-     * @param pckg
-     */
-    private int displayPackageFilePath(String pckg, int userId) {
-        try {
-            PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
-            if (info != null && info.applicationInfo != null) {
-                System.out.print("package:");
-                System.out.println(info.applicationInfo.sourceDir);
-                if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
-                    for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
-                        System.out.print("package:");
-                        System.out.println(splitSourceDir);
-                    }
-                }
-                return 0;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-        }
-        return 1;
-    }
-
-    private String nextOption() {
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        if (!arg.startsWith("-")) {
-            return null;
-        }
-        mNextArg++;
-        if (arg.equals("--")) {
-            return null;
-        }
-        if (arg.length() > 1 && arg.charAt(1) != '-') {
-            if (arg.length() > 2) {
-                mCurArgData = arg.substring(2);
-                return arg.substring(0, 2);
-            } else {
-                mCurArgData = null;
-                return arg;
-            }
-        }
-        mCurArgData = null;
-        return arg;
-    }
-
-    private String nextOptionData() {
-        if (mCurArgData != null) {
-            return mCurArgData;
-        }
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String data = mArgs[mNextArg];
-        mNextArg++;
-        return data;
-    }
-
-    private String nextArg() {
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        mNextArg++;
-        return arg;
-    }
-
-    private static int showUsage() {
-        System.err.println("usage: pm path [--user USER_ID] PACKAGE");
-        System.err.println("       pm dump PACKAGE");
-        System.err.println("       pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
-        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
-        System.err.println("               [--originating-uri URI] [---referrer URI]");
-        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
-        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
-        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
-        System.err.println("       pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
-        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
-        System.err.println("               [--originating-uri URI] [---referrer URI]");
-        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
-        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
-        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
-        System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
-        System.err.println("       pm install-commit SESSION_ID");
-        System.err.println("       pm install-abandon SESSION_ID");
-        System.err.println("       pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
-        System.err.println("       pm set-installer PACKAGE INSTALLER");
-        System.err.println("       pm move-package PACKAGE [internal|UUID]");
-        System.err.println("       pm move-primary-storage [internal|UUID]");
-        System.err.println("       pm clear [--user USER_ID] PACKAGE");
-        System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
-        System.err.println("       pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm grant [--user USER_ID] PACKAGE PERMISSION");
-        System.err.println("       pm revoke [--user USER_ID] PACKAGE PERMISSION");
-        System.err.println("       pm reset-permissions");
-        System.err.println("       pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
-        System.err.println("       pm get-app-link [--user USER_ID] PACKAGE");
-        System.err.println("       pm set-install-location [0/auto] [1/internal] [2/external]");
-        System.err.println("       pm get-install-location");
-        System.err.println("       pm set-permission-enforced PERMISSION [true|false]");
-        System.err.println("       pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
-        System.err.println("       pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME");
-        System.err.println("       pm remove-user USER_ID");
-        System.err.println("       pm get-max-users");
-        System.err.println("");
-        System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
-        System.err.println("  to display the new commands.");
-        System.err.println("");
-        System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
-        System.err.println("");
-        System.err.println("pm dump: print system state associated with the given PACKAGE.");
-        System.err.println("");
-        System.err.println("pm install: install a single legacy package");
-        System.err.println("pm install-create: create an install session");
-        System.err.println("    -l: forward lock application");
-        System.err.println("    -r: allow replacement of existing application");
-        System.err.println("    -t: allow test packages");
-        System.err.println("    -i: specify package name of installer owning the app");
-        System.err.println("    -s: install application on sdcard");
-        System.err.println("    -f: install application on internal flash");
-        System.err.println("    -d: allow version code downgrade (debuggable packages only)");
-        System.err.println("    -p: partial application install (new split on top of existing pkg)");
-        System.err.println("    -g: grant all runtime permissions");
-        System.err.println("    -S: size in bytes of entire session");
-        System.err.println("    --dont-kill: installing a new feature split, don't kill running app");
-        System.err.println("    --originating-uri: set URI where app was downloaded from");
-        System.err.println("    --referrer: set URI that instigated the install of the app");
-        System.err.println("    --pkg: specify expected package name of app being installed");
-        System.err.println("    --abi: override the default ABI of the platform");
-        System.err.println("    --instantapp: cause the app to be installed as an ephemeral install app");
-        System.err.println("    --full: cause the app to be installed as a non-ephemeral full app");
-        System.err.println("    --install-location: force the install location:");
-        System.err.println("        0=auto, 1=internal only, 2=prefer external");
-        System.err.println("    --force-uuid: force install on to disk volume with given UUID");
-        System.err.println("    --force-sdk: allow install even when existing app targets platform");
-        System.err.println("        codename but new one targets a final API level");
-        System.err.println("");
-        System.err.println("pm install-write: write a package into existing session; path may");
-        System.err.println("  be '-' to read from stdin");
-        System.err.println("    -S: size in bytes of package, required for stdin");
-        System.err.println("");
-        System.err.println("pm install-commit: perform install of fully staged session");
-        System.err.println("pm install-abandon: abandon session");
-        System.err.println("");
-        System.err.println("pm set-installer: set installer package name");
-        System.err.println("");
-        System.err.println("pm uninstall: removes a package from the system. Options:");
-        System.err.println("    -k: keep the data and cache directories around after package removal.");
-        System.err.println("");
-        System.err.println("pm clear: deletes all data associated with a package.");
-        System.err.println("");
-        System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:");
-        System.err.println("  these commands change the enabled state of a given package or");
-        System.err.println("  component (written as \"package/class\").");
-        System.err.println("");
-        System.err.println("pm grant, revoke: these commands either grant or revoke permissions");
-        System.err.println("    to apps. The permissions must be declared as used in the app's");
-        System.err.println("    manifest, be runtime permissions (protection level dangerous),");
-        System.err.println("    and the app targeting SDK greater than Lollipop MR1.");
-        System.err.println("");
-        System.err.println("pm reset-permissions: revert all runtime permissions to their default state.");
-        System.err.println("");
-        System.err.println("pm get-install-location: returns the current install location.");
-        System.err.println("    0 [auto]: Let system decide the best location");
-        System.err.println("    1 [internal]: Install on internal device storage");
-        System.err.println("    2 [external]: Install on external media");
-        System.err.println("");
-        System.err.println("pm set-install-location: changes the default install location.");
-        System.err.println("  NOTE: this is only intended for debugging; using this can cause");
-        System.err.println("  applications to break and other undersireable behavior.");
-        System.err.println("    0 [auto]: Let system decide the best location");
-        System.err.println("    1 [internal]: Install on internal device storage");
-        System.err.println("    2 [external]: Install on external media");
-        System.err.println("");
-        System.err.println("pm trim-caches: trim cache files to reach the given free space.");
-        System.err.println("");
-        System.err.println("pm create-user: create a new user with the given USER_NAME,");
-        System.err.println("  printing the new user identifier of the user.");
-        System.err.println("");
-        System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
-        System.err.println("  deleting all data associated with that user");
-        System.err.println("");
-        return 1;
-    }
-}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index d860363..1b2f9da 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -15,13 +15,13 @@
 LOCAL_PATH:= $(call my-dir)
 
 statsd_common_src := \
-    ../../core/java/android/os/IStatsCallbacks.aidl \
     ../../core/java/android/os/IStatsCompanionService.aidl \
     ../../core/java/android/os/IStatsManager.aidl \
     src/stats_log.proto \
     src/statsd_config.proto \
     src/atoms_copy.proto \
     src/anomaly/AnomalyMonitor.cpp \
+    src/anomaly/AnomalyTracker.cpp \
     src/condition/CombinationConditionTracker.cpp \
     src/condition/condition_util.cpp \
     src/condition/SimpleConditionTracker.cpp \
@@ -40,7 +40,6 @@
     src/matchers/CombinationLogMatchingTracker.cpp \
     src/matchers/matcher_util.cpp \
     src/matchers/SimpleLogMatchingTracker.cpp \
-    src/anomaly/DiscreteAnomalyTracker.cpp \
     src/metrics/MetricProducer.cpp \
     src/metrics/EventMetricProducer.cpp \
     src/metrics/CountMetricProducer.cpp \
@@ -179,4 +178,3 @@
 statsd_common_c_includes:=
 
 include $(BUILD_NATIVE_TEST)
-
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index b764ce5..fcb5107 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -50,13 +50,24 @@
 const int FIELD_ID_NAME = 2;
 
 StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
-                                     const std::function<void(const vector<uint8_t>&)>& pushLog)
-    : mUidMap(uidMap), mPushLog(pushLog) {
+                                     const sp<AnomalyMonitor>& anomalyMonitor,
+                                     const std::function<void(const ConfigKey&)>& sendBroadcast)
+    : mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast) {
 }
 
 StatsLogProcessor::~StatsLogProcessor() {
 }
 
+void StatsLogProcessor::onAnomalyAlarmFired(
+        const uint64_t timestampNs,
+        unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
+    for (const auto& anomaly : anomalySet) {
+        for (const auto& itr : mMetricsManagers) {
+            itr.second->onAnomalyAlarmFired(timestampNs, anomaly);
+        }
+    }
+}
+
 // TODO: what if statsd service restarts? How do we know what logs are already processed before?
 void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
     // pass the event to metrics managers.
@@ -93,6 +104,7 @@
     unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
     if (newMetricsManager->isConfigValid()) {
         mUidMap->OnConfigUpdated(key);
+        newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
         mMetricsManagers[key] = std::move(newMetricsManager);
         // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
         ALOGD("StatsdConfig valid");
@@ -102,12 +114,27 @@
     }
 }
 
-vector<uint8_t> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) {
     auto it = mMetricsManagers.find(key);
     if (it == mMetricsManagers.end()) {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
-        return vector<uint8_t>();
+        return 0;
     }
+    return it->second->byteSize();
+}
+
+void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) {
+    auto it = mMetricsManagers.find(key);
+    if (it == mMetricsManagers.end()) {
+        ALOGW("Config source %s does not exist", key.ToString().c_str());
+        return;
+    }
+
+    // This allows another broadcast to be sent within the rate-limit period if we get close to
+    // filling the buffer again soon.
+    mBroadcastTimesMutex.lock();
+    mLastBroadcastTimes.erase(key);
+    mBroadcastTimesMutex.unlock();
 
     ProtoOutputStream proto;
 
@@ -131,17 +158,18 @@
     uidMap.SerializeToArray(&uidMapBuffer[0], uidMapSize);
     proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP, uidMapBuffer, uidMapSize);
 
-    vector<uint8_t> buffer(proto.size());
-    size_t pos = 0;
-    auto iter = proto.data();
-    while (iter.readBuffer() != NULL) {
-        size_t toRead = iter.currentToRead();
-        std::memcpy(&buffer[pos], iter.readBuffer(), toRead);
-        pos += toRead;
-        iter.rp()->move(toRead);
+    if (outData != nullptr) {
+        outData->clear();
+        outData->resize(proto.size());
+        size_t pos = 0;
+        auto iter = proto.data();
+        while (iter.readBuffer() != NULL) {
+            size_t toRead = iter.currentToRead();
+            std::memcpy(&((*outData)[pos]), iter.readBuffer(), toRead);
+            pos += toRead;
+            iter.rp()->move(toRead);
+        }
     }
-
-    return buffer;
 }
 
 void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
@@ -151,42 +179,34 @@
         mMetricsManagers.erase(it);
         mUidMap->OnConfigRemoved(key);
     }
-    auto flushTime = mLastFlushTimes.find(key);
-    if (flushTime != mLastFlushTimes.end()) {
-        mLastFlushTimes.erase(flushTime);
-    }
+
+    std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
+    mLastBroadcastTimes.erase(key);
 }
 
 void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs,
                                          const ConfigKey& key,
                                          const unique_ptr<MetricsManager>& metricsManager) {
-    auto lastFlushNs = mLastFlushTimes.find(key);
-    if (lastFlushNs != mLastFlushTimes.end()) {
-        if (timestampNs - lastFlushNs->second < kMinFlushPeriod) {
-            return;
-        }
-    }
+    std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
 
     size_t totalBytes = metricsManager->byteSize();
-    if (totalBytes > kMaxSerializedBytes) {
-        flush();
-        mLastFlushTimes[key] = std::move(timestampNs);
+    if (totalBytes > .9 * kMaxSerializedBytes) { // Send broadcast so that receivers can pull data.
+        auto lastFlushNs = mLastBroadcastTimes.find(key);
+        if (lastFlushNs != mLastBroadcastTimes.end()) {
+            if (timestampNs - lastFlushNs->second < kMinBroadcastPeriod) {
+                return;
+            }
+        }
+        mLastBroadcastTimes[key] = timestampNs;
+        ALOGD("StatsD requesting broadcast for %s", key.ToString().c_str());
+        mSendBroadcast(key);
+    } else if (totalBytes > kMaxSerializedBytes) { // Too late. We need to start clearing data.
+        // We ignore the return value so we force each metric producer to clear its contents.
+        metricsManager->onDumpReport();
+        ALOGD("StatsD had to toss out metrics for %s", key.ToString().c_str());
     }
 }
 
-void StatsLogProcessor::flush() {
-    // TODO: Take ConfigKey as an argument and flush metrics related to the
-    // ConfigKey. Also, create a wrapper that holds a repeated field of
-    // StatsLogReport's.
-    /*
-    StatsLogReport logReport;
-    const int numBytes = logReport.ByteSize();
-    vector<uint8_t> logReportBuffer(numBytes);
-    logReport.SerializeToArray(&logReportBuffer[0], numBytes);
-    mPushLog(logReportBuffer);
-    */
-}
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index f38d715..510dc51 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -32,8 +32,8 @@
 
 class StatsLogProcessor : public ConfigListener {
 public:
-    StatsLogProcessor(const sp<UidMap>& uidMap,
-                      const std::function<void(const vector<uint8_t>&)>& pushLog);
+    StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
+                      const std::function<void(const ConfigKey&)>& sendBroadcast);
     virtual ~StatsLogProcessor();
 
     virtual void OnLogEvent(const LogEvent& event);
@@ -41,18 +41,24 @@
     void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
     void OnConfigRemoved(const ConfigKey& key);
 
-    vector<uint8_t> onDumpReport(const ConfigKey& key);
-
-    /* Request a flush through a binder call. */
-    void flush();
+    size_t GetMetricsSize(const ConfigKey& key);
+ 
+    void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
+    void onAnomalyAlarmFired(
+            const uint64_t timestampNs,
+            unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet);
 
 private:
+    mutable mutex mBroadcastTimesMutex;
+
     std::unordered_map<ConfigKey, std::unique_ptr<MetricsManager>> mMetricsManagers;
 
-    std::unordered_map<ConfigKey, long> mLastFlushTimes;
+    std::unordered_map<ConfigKey, long> mLastBroadcastTimes;
 
     sp<UidMap> mUidMap;  // Reference to the UidMap to lookup app name and version for each uid.
 
+    sp<AnomalyMonitor> mAnomalyMonitor;
+
     /* Max *serialized* size of the logs kept in memory before flushing through binder call.
        Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
        So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
@@ -60,17 +66,18 @@
      */
     static const size_t kMaxSerializedBytes = 16 * 1024;
 
-    /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush
-       the logs to callback clients if true. */
+    /* Check if we should send a broadcast if approaching memory limits and if we're over, we
+     * actually delete the data. */
     void flushIfNecessary(uint64_t timestampNs,
                           const ConfigKey& key,
                           const unique_ptr<MetricsManager>& metricsManager);
 
-    std::function<void(const vector<uint8_t>&)> mPushLog;
+    // Function used to send a broadcast so that receiver for the config key can call getData
+    // to retrieve the stored data.
+    std::function<void(const ConfigKey& key)> mSendBroadcast;
 
-    /* Minimum period between two flushes in nanoseconds. Currently set to 10
-     * minutes. */
-    static const unsigned long long kMinFlushPeriod = 600 * NS_PER_SEC;
+    /* Minimum period between two broadcasts in nanoseconds. Currently set to 60 seconds. */
+    static const unsigned long long kMinBroadcastPeriod = 60 * NS_PER_SEC;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index a0b2340..1a056df 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -17,6 +17,7 @@
 #define DEBUG true
 #include "Log.h"
 
+#include "android-base/stringprintf.h"
 #include "StatsService.h"
 #include "config/ConfigKey.h"
 #include "config/ConfigManager.h"
@@ -25,11 +26,11 @@
 #include <android-base/file.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
+#include <dirent.h>
 #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
 #include <private/android_filesystem_config.h>
 #include <utils/Looper.h>
 #include <utils/String16.h>
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/system_properties.h>
@@ -42,6 +43,7 @@
 namespace statsd {
 
 constexpr const char* kPermissionDump = "android.permission.DUMP";
+#define STATS_SERVICE_DIR "/data/system/stats-service"
 
 // ======================================================================
 /**
@@ -71,8 +73,18 @@
 {
     mUidMap = new UidMap();
     mConfigManager = new ConfigManager();
-    mProcessor = new StatsLogProcessor(mUidMap, [](const vector<uint8_t>& log) {
-        // TODO: Update how we send data out of StatsD.
+    mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) {
+        auto sc = getStatsCompanionService();
+        auto receiver = mConfigManager->GetConfigReceiver(key);
+        if (sc == nullptr) {
+            ALOGD("Could not find StatsCompanionService");
+        } else if (receiver.first.size() == 0) {
+            ALOGD("Statscompanion could not find a broadcast receiver for %s",
+                  key.ToString().c_str());
+        } else {
+            sc->sendBroadcast(String16(receiver.first.c_str()),
+                              String16(receiver.second.c_str()));
+        }
     });
 
     mConfigManager->AddListener(mProcessor);
@@ -204,7 +216,15 @@
         }
 
         if (!args[0].compare(String8("send-broadcast"))) {
-            return cmd_trigger_broadcast(args);
+            return cmd_trigger_broadcast(out, args);
+        }
+
+        if (!args[0].compare(String8("print-stats"))) {
+            return cmd_print_stats(out);
+        }
+
+        if (!args[0].compare(String8("clear-config"))) {
+            return cmd_remove_config_files(out);
         }
     }
 
@@ -223,7 +243,12 @@
     fprintf(out, "  Prints the UID, app name, version mapping.\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
-    fprintf(out, "usage: adb shell cmds stats pull-source [int] \n");
+    fprintf(out, "usage: adb shell cmd stats clear-config \n");
+    fprintf(out, "\n");
+    fprintf(out, "  Removes all configs from disk.\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats pull-source [int] \n");
     fprintf(out, "\n");
     fprintf(out, "  Prints the output of a pulled metrics source (int indicates source)\n");
     fprintf(out, "\n");
@@ -248,16 +273,62 @@
     fprintf(out, "  NAME          The name of the configuration\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
-    fprintf(out, "usage: adb shell cmd stats send-broadcast PACKAGE CLASS\n");
-    fprintf(out, "  Send a broadcast that triggers one subscriber to fetch metrics.\n");
-    fprintf(out, "  PACKAGE        The name of the package to receive the broadcast.\n");
-    fprintf(out, "  CLASS          The name of the class to receive the broadcast.\n");
+    fprintf(out, "usage: adb shell cmd stats send-broadcast [UID] NAME\n");
+    fprintf(out, "  Send a broadcast that triggers the subscriber to fetch metrics.\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");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats print-stats\n");
+    fprintf(out, "  Prints some basic stats.\n");
 }
 
-status_t StatsService::cmd_trigger_broadcast(Vector<String8>& args) {
-    auto sc = getStatsCompanionService();
-    sc->sendBroadcast(String16(args[1]), String16(args[2]));
-    ALOGD("StatsService::trigger broadcast succeeded");
+status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) {
+    string name;
+    bool good = false;
+    int uid;
+    const int argCount = args.size();
+    if (argCount == 2) {
+        // Automatically pick the UID
+        uid = IPCThreadState::self()->getCallingUid();
+        // TODO: What if this isn't a binder call? Should we fail?
+        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
+        // impersonate other uids.
+        if (mEngBuild) {
+            const char* s = args[1].c_str();
+            if (*s != '\0') {
+                char* end = NULL;
+                uid = strtol(s, &end, 0);
+                if (*end == '\0') {
+                    name.assign(args[2].c_str(), args[2].size());
+                    good = true;
+                }
+            }
+        } else {
+            fprintf(out,
+                    "The metrics can only be dumped for other UIDs on eng or userdebug "
+                            "builds.\n");
+        }
+    }
+    if (!good) {
+        print_cmd_help(out);
+        return UNKNOWN_ERROR;
+    }
+    auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, name));
+    sp<IStatsCompanionService> sc = getStatsCompanionService();
+    if (sc != nullptr) {
+        sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str()));
+        ALOGD("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
+              args[2].c_str());
+    } else {
+        ALOGD("Could not access statsCompanion");
+    }
+
     return NO_ERROR;
 }
 
@@ -362,7 +433,8 @@
             }
         }
         if (good) {
-            mProcessor->onDumpReport(ConfigKey(uid, name));
+            vector<uint8_t> data;
+            mProcessor->onDumpReport(ConfigKey(uid, name), &data);
             // TODO: print the returned StatsLogReport to file instead of printing to logcat.
             fprintf(out, "Dump report for Config [%d,%s]\n", uid, name.c_str());
             fprintf(out, "See the StatsLogReport in logcat...\n");
@@ -378,6 +450,15 @@
     }
 }
 
+status_t StatsService::cmd_print_stats(FILE* out) {
+    vector<ConfigKey> configs = mConfigManager->GetAllConfigKeys();
+    for (const ConfigKey& key : configs) {
+        fprintf(out, "Config %s uses %zu bytes\n", key.ToString().c_str(),
+                mProcessor->GetMetricsSize(key));
+    }
+    return NO_ERROR;
+}
+
 status_t StatsService::cmd_print_stats_log(FILE* out, const Vector<String8>& args) {
     long msec = 0;
 
@@ -405,6 +486,27 @@
     return UNKNOWN_ERROR;
 }
 
+status_t StatsService::cmd_remove_config_files(FILE* out) {
+    fprintf(out, "Trying to remove config files...\n");
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
+    if (dir == NULL) {
+        fprintf(out, "No existing config files found exiting...\n");
+        return NO_ERROR;
+    }
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') continue;
+        string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
+        fprintf(out, "Deleting file %s\n", file_name.c_str());
+        if (remove(file_name.c_str())) {
+            fprintf(out, "Error deleting file %s\n", file_name.c_str());
+        }
+    }
+    return NO_ERROR;
+}
+
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
                                       const vector<String16>& app) {
     if (DEBUG) ALOGD("StatsService::informAllUidData was called");
@@ -452,7 +554,10 @@
 
     if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
     // TODO: check through all counters/timers and see if an anomaly has indeed occurred.
-
+    uint64_t currentTimeNs = time(nullptr) * NS_PER_SEC;
+    std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
+            mAnomalyMonitor->onAlarmFired(currentTimeNs);
+    mProcessor->onAnomalyAlarmFired(currentTimeNs, anomalySet);
     return Status::ok();
 }
 
@@ -541,12 +646,14 @@
     mProcessor->OnLogEvent(event);
 }
 
-Status StatsService::getData(const String16& key, vector<uint8_t>* output) {
+Status StatsService::getData(const String16& key, vector <uint8_t>* output) {
     IPCThreadState* ipc = IPCThreadState::self();
-    if (checkCallingPermission(String16(kPermissionDump),
-                               reinterpret_cast<int32_t*>(ipc->getCallingPid()),
-                               reinterpret_cast<int32_t*>(ipc->getCallingUid()))) {
-        // TODO: Implement this.
+    ALOGD("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(),
+          ipc->getCallingUid());
+    if (checkCallingPermission(String16(kPermissionDump))) {
+        string keyStr = string(String8(key).string());
+        ConfigKey configKey(ipc->getCallingUid(), keyStr);
+        mProcessor->onDumpReport(configKey, output);
         return Status::ok();
     } else {
         return Status::fromExceptionCode(binder::Status::EX_SECURITY);
@@ -558,11 +665,9 @@
                                       const String16& package, const String16& cls,
                                       bool* success) {
     IPCThreadState* ipc = IPCThreadState::self();
-    int32_t* uid = reinterpret_cast<int32_t*>(ipc->getCallingUid());
-    if (checkCallingPermission(String16(kPermissionDump),
-                               reinterpret_cast<int32_t*>(ipc->getCallingPid()), uid)) {
+    if (checkCallingPermission(String16(kPermissionDump))) {
         string keyString = string(String8(key).string());
-        ConfigKey configKey(*uid, keyString);
+        ConfigKey configKey(ipc->getCallingUid(), keyString);
         StatsdConfig cfg;
         cfg.ParseFromArray(&config[0], config.size());
         mConfigManager->UpdateConfig(configKey, cfg);
@@ -577,10 +682,9 @@
 
 Status StatsService::removeConfiguration(const String16& key, bool* success) {
     IPCThreadState* ipc = IPCThreadState::self();
-    if (checkCallingPermission(String16(kPermissionDump),
-                               reinterpret_cast<int32_t*>(ipc->getCallingPid()),
-                               reinterpret_cast<int32_t*>(ipc->getCallingUid()))) {
-        // TODO: Implement this.
+    if (checkCallingPermission(String16(kPermissionDump))) {
+        string keyStr = string(String8(key).string());
+        mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), keyStr));
         return Status::ok();
     } else {
         *success = false;
@@ -588,13 +692,7 @@
     }
 }
 
-void StatsService::binderDied(const wp<IBinder>& who) {
-    for (size_t i = 0; i < mCallbacks.size(); i++) {
-        if (IInterface::asBinder(mCallbacks[i]) == who) {
-            mCallbacks.removeAt(i);
-            break;
-        }
-    }
+void StatsService::binderDied(const wp <IBinder>& who) {
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index c3729de..4d768f6 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -24,7 +24,6 @@
 #include "packages/UidMap.h"
 
 #include <android/os/BnStatsManager.h>
-#include <android/os/IStatsCallbacks.h>
 #include <android/os/IStatsCompanionService.h>
 #include <binder/IResultReceiver.h>
 #include <utils/Looper.h>
@@ -124,7 +123,7 @@
     /**
      * Trigger a broadcast.
      */
-    status_t cmd_trigger_broadcast(Vector<String8>& args);
+    status_t cmd_trigger_broadcast(FILE* out, Vector<String8>& args);
 
     /**
      * Handle the config sub-command.
@@ -132,6 +131,11 @@
     status_t cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args);
 
     /**
+     * Prints some basic stats to std out.
+     */
+    status_t cmd_print_stats(FILE* out);
+
+    /**
      * Print the event log.
      */
     status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
@@ -152,6 +156,11 @@
     status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
 
     /**
+     * Removes all configs stored on disk.
+     */
+    status_t cmd_remove_config_files(FILE* out);
+
+    /**
      * Update a configuration.
      */
     void set_config(int uid, const string& name, const StatsdConfig& config);
@@ -185,16 +194,6 @@
      * Whether this is an eng build.
      */
     bool mEngBuild;
-
-    /**
-     * Lock for callback handling.
-     */
-    std::mutex mLock;
-
-    /**
-     * Vector maintaining the list of callbacks for clients.
-     */
-    Vector< sp<IStatsCallbacks> > mCallbacks;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
index 7a46410..da52a9d 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -129,6 +129,11 @@
     return ((int64_t)timeSec) * 1000;
 }
 
+unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::onAlarmFired(
+        uint64_t timestampNs) {
+    return popSoonerThan(static_cast<uint32_t>(timestampNs));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index d9207e9..0bd5055 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANOMALY_MONITOR_H
-#define ANOMALY_MONITOR_H
+#pragma once
 
 #include "anomaly/indexed_priority_queue.h"
 
@@ -23,6 +22,8 @@
 #include <utils/RefBase.h>
 
 #include <queue>
+#include <set>
+#include <unordered_map>
 #include <unordered_set>
 #include <vector>
 
@@ -114,6 +115,8 @@
         return mRegisteredAlarmTimeSec;
     }
 
+    unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> onAlarmFired(uint64_t timestampNs);
+
 private:
     std::mutex mLock;
 
@@ -154,5 +157,3 @@
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
-
-#endif  // ANOMALY_MONITOR_H
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
new file mode 100644
index 0000000..0904a04
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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 "AnomalyTracker.h"
+
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AnomalyTracker::AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs)
+    : mAlert(alert),
+      mBucketSizeNs(bucketSizeNs),
+      mNumOfPastPackets(mAlert.number_of_buckets() - 1) {
+    VLOG("AnomalyTracker() called");
+    if (mAlert.number_of_buckets() <= 0) {
+        ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
+              (long long)mAlert.number_of_buckets());
+        return;
+    }
+    if (mBucketSizeNs <= 0) {
+        ALOGE("Cannot create DiscreteAnomalyTracker with bucket size %lld ",
+              (long long)mBucketSizeNs);
+        return;
+    }
+    if (!mAlert.has_trigger_if_sum_gt()) {
+        ALOGE("Cannot create DiscreteAnomalyTracker without threshold");
+        return;
+    }
+    reset(); // initialization
+}
+
+AnomalyTracker::~AnomalyTracker() {
+    VLOG("~AnomalyTracker() called");
+    stopAllAlarms();
+}
+
+void AnomalyTracker::reset() {
+    VLOG("reset() called.");
+    stopAllAlarms();
+    mPastBuckets.clear();
+    // Excludes the current bucket.
+    mPastBuckets.resize(mNumOfPastPackets);
+    mSumOverPastBuckets.clear();
+    mMostRecentBucketNum = -1;
+    mLastAlarmTimestampNs = -1;
+}
+
+size_t AnomalyTracker::index(int64_t bucketNum) const {
+    return bucketNum % mNumOfPastPackets;
+}
+
+void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
+    VLOG("addPastBucket() called.");
+    if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastPackets) {
+        ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
+        return;
+    }
+
+    // The past packets are ancient. Empty out old mPastBuckets[i] values and reset
+    // mSumOverPastBuckets.
+    if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastPackets) {
+        mPastBuckets.clear();
+        mPastBuckets.resize(mNumOfPastPackets);
+        mSumOverPastBuckets.clear();
+    } else {
+        for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastPackets + 1));
+             i <= latestPastBucketNum - mNumOfPastPackets; i++) {
+            const int idx = index(i);
+            subtractBucketFromSum(mPastBuckets[idx]);
+            mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
+        }
+    }
+
+    // It is an update operation.
+    if (latestPastBucketNum <= mMostRecentBucketNum &&
+        latestPastBucketNum > mMostRecentBucketNum - mNumOfPastPackets) {
+        subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
+    }
+}
+
+void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+                                   const int64_t& bucketNum) {
+    flushPastBuckets(bucketNum);
+
+    auto& bucket = mPastBuckets[index(bucketNum)];
+    if (bucket == nullptr) {
+        bucket = std::make_shared<DimToValMap>();
+    }
+    bucket->insert({key, bucketValue});
+    addBucketToSum(bucket);
+    mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
+}
+
+void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
+                                   const int64_t& bucketNum) {
+    VLOG("addPastBucket() called.");
+    flushPastBuckets(bucketNum);
+    // Replace the oldest bucket with the new bucket we are adding.
+    mPastBuckets[index(bucketNum)] = bucketValues;
+    addBucketToSum(bucketValues);
+    mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
+}
+
+void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
+    if (bucket == nullptr) {
+        return;
+    }
+    // For each dimension present in the bucket, subtract its value from its corresponding sum.
+    for (const auto& keyValuePair : *bucket) {
+        auto itr = mSumOverPastBuckets.find(keyValuePair.first);
+        if (itr == mSumOverPastBuckets.end()) {
+            continue;
+        }
+        itr->second -= keyValuePair.second;
+        // TODO: No need to look up the object twice like this. Use a var.
+        if (itr->second == 0) {
+            mSumOverPastBuckets.erase(itr);
+        }
+    }
+}
+
+void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
+    if (bucket == nullptr) {
+        return;
+    }
+    // For each dimension present in the bucket, add its value to its corresponding sum.
+    for (const auto& keyValuePair : *bucket) {
+        mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
+    }
+}
+
+int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
+                                           const int64_t& bucketNum) const {
+    const auto& bucket = mPastBuckets[index(bucketNum)];
+    if (bucket == nullptr) {
+        return 0;
+    }
+    const auto& itr = bucket->find(key);
+    return itr == bucket->end() ? 0 : itr->second;
+}
+
+int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
+    const auto& itr = mSumOverPastBuckets.find(key);
+    if (itr != mSumOverPastBuckets.end()) {
+        return itr->second;
+    }
+    return 0;
+}
+
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
+                                   const DimToValMap& currentBucket) {
+    if (currentBucketNum > mMostRecentBucketNum + 1) {
+        addPastBucket(nullptr, currentBucketNum - 1);
+    }
+    for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
+        if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
+            return true;
+        }
+    }
+    // In theory, we also need to check the dimsions not in the current bucket. In single-thread
+    // mode, usually we could avoid the following loops.
+    for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
+        if (itr->second > mAlert.trigger_if_sum_gt()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
+                                   const int64_t& currentBucketValue) {
+    if (currentBucketNum > mMostRecentBucketNum + 1) {
+        addPastBucket(key, 0, currentBucketNum - 1);
+    }
+    return getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
+}
+
+void AnomalyTracker::declareAnomaly(const uint64_t& timestamp) {
+    if (mLastAlarmTimestampNs >= 0 &&
+        timestamp - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC) {
+        VLOG("Skipping anomaly check since within refractory period");
+        return;
+    }
+    // TODO(guardrail): Consider guarding against too short refractory periods.
+    mLastAlarmTimestampNs = timestamp;
+
+    if (mAlert.has_incidentd_details()) {
+        // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+        ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+    } else {
+        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+    }
+}
+
+void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+                                                  const uint64_t& timestamp) {
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr == mAlarms.end()) {
+        return;
+    }
+
+    if (itr->second != nullptr &&
+        static_cast<uint32_t>(timestamp / NS_PER_SEC) >= itr->second->timestampSec) {
+        declareAnomaly(timestamp);
+        stopAlarm(dimensionKey);
+    }
+}
+
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+                                             const int64_t& currBucketNum,
+                                             const HashableDimensionKey& key,
+                                             const int64_t& currentBucketValue) {
+    if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
+        declareAnomaly(timestamp);
+    }
+}
+
+void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
+                                             const int64_t& currBucketNum,
+                                             const DimToValMap& currentBucket) {
+    if (detectAnomaly(currBucketNum, currentBucket)) {
+        declareAnomaly(timestamp);
+    }
+}
+
+void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
+                                const uint64_t& timestamp) {
+    sp<const AnomalyAlarm> alarm = new AnomalyAlarm{static_cast<uint32_t>(timestamp / NS_PER_SEC)};
+    mAlarms.insert({dimensionKey, alarm});
+    if (mAnomalyMonitor != nullptr) {
+        mAnomalyMonitor->add(alarm);
+    }
+}
+
+void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
+    auto itr = mAlarms.find(dimensionKey);
+    if (itr != mAlarms.end()) {
+        mAlarms.erase(dimensionKey);
+    }
+    if (mAnomalyMonitor != nullptr) {
+        mAnomalyMonitor->remove(itr->second);
+    }
+}
+
+void AnomalyTracker::stopAllAlarms() {
+    std::set<HashableDimensionKey> keys;
+    for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
+        keys.insert(itr->first);
+    }
+    for (auto key : keys) {
+        stopAlarm(key);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
new file mode 100644
index 0000000..ce6c995
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include "AnomalyMonitor.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
+#include "stats_util.h"  // HashableDimensionKey and DimToValMap
+
+#include <memory> // unique_ptr
+#include <stdlib.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::shared_ptr;
+
+// This anomaly track assmues that all values are non-negative.
+class AnomalyTracker : public virtual RefBase {
+public:
+    AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs);
+
+    virtual ~AnomalyTracker();
+
+    // Adds a bucket.
+    // Bucket index starts from 0.
+    void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
+    void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
+                       const int64_t& bucketNum);
+
+    // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
+    bool detectAnomaly(const int64_t& currBucketNum, const DimToValMap& currentBucket);
+    bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
+                       const int64_t& currentBucketValue);
+
+    // Informs incidentd about the detected alert.
+    void declareAnomaly(const uint64_t& timestamp);
+
+    // Detects the alert and informs the incidentd when applicable.
+    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+                                 const DimToValMap& currentBucket);
+    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+                                 const HashableDimensionKey& key,
+                                 const int64_t& currentBucketValue);
+
+    // Starts the alarm at the given timestamp.
+    void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
+    // Stops the alarm.
+    void stopAlarm(const HashableDimensionKey& dimensionKey);
+
+    // Stop all the alarms owned by this tracker.
+    void stopAllAlarms();
+
+    // Init the anmaly monitor which is shared across anomaly trackers.
+    inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+        mAnomalyMonitor = anomalyMonitor;
+    }
+
+    // Declares the anomaly when the alarm expired given the current timestamp.
+    void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+                                      const uint64_t& timestamp);
+
+    // Helper function to return the sum value of past buckets at given dimension.
+    int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
+
+    // Helper function to return the value for a past bucket.
+    int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const;
+
+    // Returns the anomaly threshold.
+    inline int64_t getAnomalyThreshold() const {
+        return mAlert.trigger_if_sum_gt();
+    }
+
+    // Helper function to return the last alarm timestamp.
+    inline int64_t getLastAlarmTimestampNs() const {
+        return mLastAlarmTimestampNs;
+    }
+
+    inline int getNumOfPastPackets() const {
+        return mNumOfPastPackets;
+    }
+
+protected:
+    void flushPastBuckets(const int64_t& currBucketNum);
+    // statsd_config.proto Alert message that defines this tracker.
+    const Alert mAlert;
+
+    // Bucket duration in ns.
+    int64_t mBucketSizeNs = 0;
+
+    // The number of past packets to track in the anomaly detection.
+    int mNumOfPastPackets = 0;
+
+    // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
+    // are still active.
+    std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+
+    // Anomaly alarm monitor.
+    sp<AnomalyMonitor> mAnomalyMonitor;
+
+    // The exisiting bucket list.
+    std::vector<shared_ptr<DimToValMap>> mPastBuckets;
+
+    // Sum over all existing buckets cached in mPastBuckets.
+    DimToValMap mSumOverPastBuckets;
+
+    // The bucket number of the last added bucket.
+    int64_t mMostRecentBucketNum = -1;
+
+    // The timestamp when the last anomaly was declared.
+    int64_t mLastAlarmTimestampNs = -1;
+
+    // Add the information in the given bucket to mSumOverPastBuckets.
+    void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
+
+    // Subtract the information in the given bucket from mSumOverPastBuckets
+    // and remove any items with value 0.
+    void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
+
+    // Calculates the corresponding bucket index within the circular array.
+    size_t index(int64_t bucketNum) const;
+
+    // Resets all data. For use when all the data gets stale.
+    void reset();
+
+    FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
+    FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
+    FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
+    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
deleted file mode 100644
index 6492177..0000000
--- a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG true  // STOPSHIP if true
-#include "Log.h"
-
-#include "DiscreteAnomalyTracker.h"
-
-#include <time.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-DiscreteAnomalyTracker::DiscreteAnomalyTracker(const Alert& alert) : mAlert(alert) {
-    VLOG("DiscreteAnomalyTracker() called");
-    if (mAlert.number_of_buckets() <= 0) {
-        ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
-              (long long)mAlert.number_of_buckets());
-        return;
-    }
-    mPastBuckets.resize(mAlert.number_of_buckets());
-    reset(); // initialization
-}
-
-DiscreteAnomalyTracker::~DiscreteAnomalyTracker() {
-    VLOG("~DiscreteAnomalyTracker() called");
-}
-
-void DiscreteAnomalyTracker::reset() {
-    VLOG("reset() called.");
-    mPastBuckets.clear();
-    mPastBuckets.resize(mAlert.number_of_buckets());
-    mSumOverPastBuckets.clear();
-    mCurrentBucketIndex = -1;
-    mLastAlarmAtBucketIndex = -1;
-    mAnomalyDeclared = 0;
-}
-
-size_t DiscreteAnomalyTracker::index(int64_t bucketNum) {
-    return bucketNum % mAlert.number_of_buckets();
-}
-
-void DiscreteAnomalyTracker::addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues,
-                                               int64_t bucketIndex) {
-    VLOG("addPastBucket() called.");
-    if (bucketIndex <= mCurrentBucketIndex - mAlert.number_of_buckets()) {
-        ALOGE("Cannot add a past bucket %lld units in past", (long long)bucketIndex);
-        return;
-    }
-
-    // Empty out old mPastBuckets[i] values and update mSumOverPastBuckets.
-    if (bucketIndex - mCurrentBucketIndex >= mAlert.number_of_buckets()) {
-        mPastBuckets.clear();
-        mPastBuckets.resize(mAlert.number_of_buckets());
-        mSumOverPastBuckets.clear();
-    } else {
-        for (int64_t i = std::max(
-                     0LL, (long long)(mCurrentBucketIndex - mAlert.number_of_buckets() + 1));
-             i < bucketIndex - mAlert.number_of_buckets(); i++) {
-            const int idx = index(i);
-            subtractBucketFromSum(mPastBuckets[idx]);
-            mPastBuckets[idx] = nullptr;  // release (but not clear) the old bucket.
-        }
-    }
-    subtractBucketFromSum(mPastBuckets[index(bucketIndex)]);
-    mPastBuckets[index(bucketIndex)] = nullptr;  // release (but not clear) the old bucket.
-
-    // Replace the oldest bucket with the new bucket we are adding.
-    mPastBuckets[index(bucketIndex)] = BucketValues;
-    addBucketToSum(BucketValues);
-
-    mCurrentBucketIndex = std::max(mCurrentBucketIndex, bucketIndex);
-}
-
-void DiscreteAnomalyTracker::subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket) {
-    if (bucket == nullptr) {
-        return;
-    }
-    // For each dimension present in the bucket, subtract its value from its corresponding sum.
-    for (const auto& keyValuePair : *bucket) {
-        auto itr = mSumOverPastBuckets.find(keyValuePair.first);
-        if (itr == mSumOverPastBuckets.end()) {
-            continue;
-        }
-        itr->second -= keyValuePair.second;
-        // TODO: No need to look up the object twice like this. Use a var.
-        if (itr->second == 0) {
-            mSumOverPastBuckets.erase(itr);
-        }
-    }
-}
-
-void DiscreteAnomalyTracker::addBucketToSum(const shared_ptr<const DimToValMap>& bucket) {
-    if (bucket == nullptr) {
-        return;
-    }
-    // For each dimension present in the bucket, add its value to its corresponding sum.
-    for (const auto& keyValuePair : *bucket) {
-        mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
-    }
-}
-
-bool DiscreteAnomalyTracker::detectAnomaly() {
-    for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
-        if (mAlert.has_trigger_if_sum_gt() && itr->second > mAlert.trigger_if_sum_gt()) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void DiscreteAnomalyTracker::declareAndDeclareAnomaly() {
-    if (detectAnomaly()) {
-        declareAnomaly();
-    }
-}
-
-void DiscreteAnomalyTracker::declareAnomaly() {
-    if (mLastAlarmAtBucketIndex >= 0 && mCurrentBucketIndex - mLastAlarmAtBucketIndex <=
-                                        (long long)mAlert.refractory_period_in_buckets()) {
-        VLOG("Skipping anomaly check since within refractory period");
-        return;
-    }
-    mAnomalyDeclared++;
-    // TODO(guardrail): Consider guarding against too short refractory periods.
-    mLastAlarmAtBucketIndex = mCurrentBucketIndex;
-
-    if (mAlert.has_incidentd_details()) {
-        // TODO: Can construct a name based on the criteria (and/or relay the criteria).
-        ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
-        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
-    } else {
-        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
deleted file mode 100644
index ed7d5d7..0000000
--- a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
+++ /dev/null
@@ -1,91 +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.
- */
-
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
-#include "stats_util.h" // HashableDimensionKey and DimToValMap
-
-#include <memory> // unique_ptr
-#include <stdlib.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::unordered_map;
-using std::shared_ptr;
-
-// This anomaly track assmues that all values are non-negative.
-class DiscreteAnomalyTracker {
- public:
-    DiscreteAnomalyTracker(const Alert& alert);
-
-    virtual ~DiscreteAnomalyTracker();
-
-    // Adds a new bucket or updates an existing bucket.
-    // Bucket index starts from 0.
-    void addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues, int64_t bucketIndex);
-
-    // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
-    bool detectAnomaly();
-
-    // Informs incidentd about the detected alert.
-    void declareAnomaly();
-
-    // Detects the alert and informs the incidentd when applicable.
-    void declareAndDeclareAnomaly();
-
-private:
-    // statsd_config.proto Alert message that defines this tracker.
-    const Alert mAlert;
-
-    // The exisiting bucket list.
-    std::vector<shared_ptr<const DimToValMap>> mPastBuckets;
-
-    // Sum over all existing buckets cached in mPastBuckets.
-    DimToValMap mSumOverPastBuckets;
-
-    // Current bucket index of the current anomaly detection window. Bucket index starts from 0.
-    int64_t mCurrentBucketIndex = -1;
-
-    // The bucket index when the last anomaly was declared.
-    int64_t mLastAlarmAtBucketIndex = -1;
-
-    // The total number of declared anomalies.
-    int64_t mAnomalyDeclared = 0;
-
-    // Add the information in the given bucket to mSumOverPastBuckets.
-    void addBucketToSum(const shared_ptr<const DimToValMap>& bucket);
-
-    // Subtract the information in the given bucket from mSumOverPastBuckets
-    // and remove any items with value 0.
-    void subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket);
-
-    // Calculates the corresponding bucket index within the circular array.
-    size_t index(int64_t bucketNum);
-
-    // Resets all data. For use when all the data gets stale.
-    void reset();
-
-    FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
-    FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/anomaly/indexed_priority_queue.h b/cmds/statsd/src/anomaly/indexed_priority_queue.h
index 1a2e9c2..4982d4b 100644
--- a/cmds/statsd/src/anomaly/indexed_priority_queue.h
+++ b/cmds/statsd/src/anomaly/indexed_priority_queue.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "Log.h"
-
 #include <utils/RefBase.h>
 #include <unordered_map>
 #include <vector>
@@ -28,7 +26,7 @@
 namespace os {
 namespace statsd {
 
-/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
+/** Defines a hash function for sp<const AA>, returning the hash of the underlying pointer. */
 template <class AA>
 struct SpHash {
     size_t operator()(const sp<const AA>& k) const {
@@ -39,7 +37,7 @@
 /**
  * Min priority queue for generic type AA.
  * Unlike a regular priority queue, this class is also capable of removing interior elements.
- * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
+ * @tparam Comparator must implement [bool operator()(sp< AA> a, sp< AA> b)], returning
  *    whether a should be closer to the top of the queue than b.
  */
 template <class AA, class Comparator>
@@ -104,7 +102,6 @@
     if (!contains(a)) return;
     size_t idx = indices[a];
     if (idx >= pq.size()) {
-        ALOGE("indexed_priority_queue: Invalid index in map of indices.");
         return;
     }
     if (idx == size()) {  // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
@@ -193,7 +190,6 @@
 template <class AA, class Comparator>
 bool indexed_priority_queue<AA, Comparator>::higher(size_t idx1, size_t idx2) const {
     if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
-        ALOGE("indexed_priority_queue: Attempting to access invalid index");
         return false;  // got to do something.
     }
     return Comparator()(pq[idx1], pq[idx2]);
@@ -208,7 +204,6 @@
 template <class AA, class Comparator>
 void indexed_priority_queue<AA, Comparator>::swap_indices(size_t i, size_t j) {
     if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
-        ALOGE("indexed_priority_queue: Attempting to swap invalid index");
         return;
     }
     sp<const AA> val_i = pq[i];
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index ba93feb..57a92b6 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -73,6 +73,7 @@
         SettingChanged setting_changed = 41;
         ActivityForegroundStateChanged activity_foreground_state_changed = 42;
         IsolatedUidChanged isolated_uid_changed = 43;
+        PacketWakeupOccurred packet_wakeup_occurred = 44;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
@@ -906,3 +907,32 @@
     optional uint64 freq_idx = 2;
     optional uint64 time_ms = 3;
 }
+
+/*
+ * Logs the reception of an incoming network packet causing the main system to wake up for
+ * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd
+ * and processed by WakeupController.cpp.
+ */
+message PacketWakeupOccurred {
+    // The uid owning the socket into which the packet was delivered, or -1 if the packet was
+    // delivered nowhere.
+    optional int32 uid = 1;
+    // The interface name on which the packet was received.
+    optional string iface = 2;
+    // The ethertype value of the packet.
+    optional int32 ethertype = 3;
+    // String representation of the destination MAC address of the packet.
+    optional string destination_hardware_address = 4;
+    // String representation of the source address of the packet if this was an IP packet.
+    optional string source_ip = 5;
+    // String representation of the destination address of the packet if this was an IP packet.
+    optional string destination_ip = 6;
+    // The value of the protocol field if this was an IPv4 packet or the value of the Next Header
+    // field if this was an IPv6 packet. The range of possible values is the same for both IP
+    // families.
+    optional int32 ip_next_header = 7;
+    // The source port if this was a TCP or UDP packet.
+    optional int32 source_port = 8;
+    // The destination port if this was a TCP or UDP packet.
+    optional int32 destination_port = 9;
+}
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index 2618a21..669a4b7 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -86,6 +86,9 @@
         case LogicalOperation::NOR:
             newCondition = hasTrue ? ConditionState::kFalse : ConditionState::kTrue;
             break;
+        case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED:
+            newCondition = ConditionState::kFalse;
+            break;
     }
     return newCondition;
 }
diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h
index bbf20fd..3489c43 100644
--- a/cmds/statsd/src/config/ConfigKey.h
+++ b/cmds/statsd/src/config/ConfigKey.h
@@ -78,7 +78,7 @@
 
 /**
  * A hash function for ConfigKey so it can be used for unordered_map/set.
- * Unfortunately this hast to go in std namespace because C++ is fun!
+ * Unfortunately this has to go in std namespace because C++ is fun!
  */
 namespace std {
 
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index a9ce4a3..2125609 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -18,16 +18,23 @@
 
 #include "stats_util.h"
 
-#include <vector>
-
+#include <android-base/file.h>
+#include <dirent.h>
 #include <stdio.h>
+#include <vector>
+#include "android-base/stringprintf.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
+#define STATS_SERVICE_DIR "/data/system/stats-service"
+
 static StatsdConfig build_fake_config();
 
+using android::base::StringPrintf;
+using std::unique_ptr;
+
 ConfigManager::ConfigManager() {
 }
 
@@ -35,11 +42,10 @@
 }
 
 void ConfigManager::Startup() {
-    // TODO: Implement me -- read from storage and call onto all of the listeners.
-    // Instead, we'll just make a fake one.
+    readConfigFromDisk();
 
     // this should be called from StatsService when it receives a statsd_config
-    UpdateConfig(ConfigKey(0, "fake"), build_fake_config());
+    UpdateConfig(ConfigKey(1000, "fake"), build_fake_config());
 }
 
 void ConfigManager::AddListener(const sp<ConfigListener>& listener) {
@@ -52,7 +58,7 @@
     // Why doesn't this work? mConfigs.insert({key, config});
 
     // Save to disk
-    update_saved_configs();
+    update_saved_configs(key, config);
 
     // Tell everyone
     for (auto& listener : mListeners) {
@@ -74,15 +80,33 @@
         // Remove from map
         mConfigs.erase(it);
 
-        // Save to disk
-        update_saved_configs();
-
         // Tell everyone
         for (auto& listener : mListeners) {
             listener->OnConfigRemoved(key);
         }
     }
-    // If we didn't find it, just quietly ignore it.
+
+    // Remove from disk. There can still be a lingering file on disk so we check
+    // whether or not the config was on memory.
+    remove_saved_configs(key);
+}
+
+void ConfigManager::remove_saved_configs(const ConfigKey& key) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
+    if (dir == NULL) {
+        ALOGD("no default config on disk");
+        return;
+    }
+    string prefix = StringPrintf("%d-%s", key.GetUid(), key.GetName().c_str());
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] != '.' && strncmp(name, prefix.c_str(), prefix.size()) == 0) {
+            if (remove(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str()) != 0) {
+                ALOGD("no file found");
+            }
+        }
+    }
 }
 
 void ConfigManager::RemoveConfigs(int uid) {
@@ -108,18 +132,101 @@
     }
 }
 
+vector<ConfigKey> ConfigManager::GetAllConfigKeys() {
+    vector<ConfigKey> ret;
+    for (auto it = mConfigs.cbegin(); it != mConfigs.cend(); ++it) {
+        ret.push_back(it->first);
+    }
+    return ret;
+}
+
+const pair<string, string> ConfigManager::GetConfigReceiver(const ConfigKey& key) {
+    auto it = mConfigReceivers.find(key);
+    if (it == mConfigReceivers.end()) {
+        return pair<string,string>();
+    } else {
+        return it->second;
+    }
+}
+
 void ConfigManager::Dump(FILE* out) {
     fprintf(out, "CONFIGURATIONS (%d)\n", (int)mConfigs.size());
     fprintf(out, "     uid name\n");
     for (unordered_map<ConfigKey, StatsdConfig>::const_iterator it = mConfigs.begin();
          it != mConfigs.end(); it++) {
         fprintf(out, "  %6d %s\n", it->first.GetUid(), it->first.GetName().c_str());
+        auto receiverIt = mConfigReceivers.find(it->first);
+        if (receiverIt != mConfigReceivers.end()) {
+            fprintf(out, "    -> received by %s, %s\n", receiverIt->second.first.c_str(),
+                    receiverIt->second.second.c_str());
+        }
         // TODO: Print the contents of the config too.
     }
 }
 
-void ConfigManager::update_saved_configs() {
-    // TODO: Implement me -- write to disk.
+void ConfigManager::readConfigFromDisk() {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
+    if (dir == NULL) {
+        ALOGD("no default config on disk");
+        return;
+    }
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') continue;
+        ALOGD("file %s", name);
+
+        int index = 0;
+        int uid = 0;
+        string configName;
+        char* substr = strtok(name, "-");
+        // Timestamp lives at index 2 but we skip parsing it as it's not needed.
+        while (substr != nullptr && index < 2) {
+            if (index) {
+                uid = atoi(substr);
+            } else {
+                configName = substr;
+            }
+            index++;
+        }
+        if (index < 2) continue;
+        string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
+        ALOGD("full file %s", file_name.c_str());
+        int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
+        if (fd != -1) {
+            string content;
+            if (android::base::ReadFdToString(fd, &content)) {
+                StatsdConfig config;
+                if (config.ParseFromString(content)) {
+                    mConfigs[ConfigKey(uid, configName)] = config;
+                    ALOGD("map key uid=%d|name=%s", uid, name);
+                }
+            }
+            close(fd);
+        }
+    }
+}
+
+void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfig& config) {
+    mkdir(STATS_SERVICE_DIR, S_IRWXU);
+
+    // If there is a pre-existing config with same key we should first delete it.
+    remove_saved_configs(key);
+
+    // Then we save the latest config.
+    string file_name = StringPrintf("%s/%d-%s-%ld", STATS_SERVICE_DIR, key.GetUid(),
+                                    key.GetName().c_str(), time(nullptr));
+    int fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
+    if (fd != -1) {
+        const int numBytes = config.ByteSize();
+        vector<uint8_t> buffer(numBytes);
+        config.SerializeToArray(&buffer[0], numBytes);
+        int result = write(fd, &buffer[0], numBytes);
+        close(fd);
+        bool wroteKey = (result == numBytes);
+        ALOGD("wrote to file %d", wroteKey);
+    }
 }
 
 static StatsdConfig build_fake_config() {
@@ -161,7 +268,7 @@
     metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
 
     // Anomaly threshold for screen-on count.
-    Alert* alert = config.add_alerts();
+    Alert* alert = config.add_alert();
     alert->set_name("1");
     alert->set_number_of_buckets(6);
     alert->set_trigger_if_sum_gt(10);
@@ -176,7 +283,7 @@
     keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
 
     // Anomaly threshold for background count.
-    alert = config.add_alerts();
+    alert = config.add_alert();
     alert->set_name("2");
     alert->set_number_of_buckets(4);
     alert->set_trigger_if_sum_gt(30);
@@ -208,7 +315,7 @@
     DurationMetric* durationMetric = config.add_duration_metric();
     durationMetric->set_name("5");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+    durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
     durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
@@ -222,7 +329,7 @@
     durationMetric = config.add_duration_metric();
     durationMetric->set_name("6");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
+    durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
     keyMatcher = durationMetric->add_dimension();
     keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
     durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
@@ -236,7 +343,7 @@
     durationMetric = config.add_duration_metric();
     durationMetric->set_name("7");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
-    durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
+    durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
     durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
     durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
     link = durationMetric->add_links();
@@ -248,7 +355,7 @@
     durationMetric = config.add_duration_metric();
     durationMetric->set_name("8");
     durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
-    durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+    durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
     durationMetric->set_what("SCREEN_IS_ON");
 
     // Value metric to count KERNEL_WAKELOCK when screen turned on
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index c21247f9..01d7fb9 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -46,9 +46,7 @@
     virtual ~ConfigManager();
 
     /**
-     * Call to load the saved configs from disk.
-     *
-     * TODO: Implement me
+     * Initialize ConfigListener by reading from disk and get updates.
      */
     void Startup();
 
@@ -70,6 +68,16 @@
     void SetConfigReceiver(const ConfigKey& key, const string& pkg, const string& cls);
 
     /**
+     * Returns the package name and class name representing the broadcast receiver for this config.
+     */
+    const pair<string, string> GetConfigReceiver(const ConfigKey& key);
+
+    /**
+     * Returns all config keys registered.
+     */
+    vector<ConfigKey> GetAllConfigKeys();
+
+    /**
      * Erase any broadcast receiver associated with this config key.
      */
     void RemoveConfigReceiver(const ConfigKey& key);
@@ -95,7 +103,12 @@
     /**
      * Save the configs to disk.
      */
-    void update_saved_configs();
+    void update_saved_configs(const ConfigKey& key, const StatsdConfig& config);
+
+    /**
+     * Remove saved configs from disk.
+     */
+    void remove_saved_configs(const ConfigKey& key);
 
     /**
      * The Configs that have been set. Each config should
@@ -112,6 +125,11 @@
      * The ConfigListeners that will be told about changes.
      */
     vector<sp<ConfigListener>> mListeners;
+
+    /**
+     * Call to load the saved configs from disk.
+     */
+    void readConfigFromDisk();
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
index e004d21..e2745d2 100644
--- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
@@ -71,10 +71,9 @@
     do {
       timeMs = std::stoull(pch);
       auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ_PULLED, timestamp);
-      auto elemList = ptr->GetAndroidLogEventList();
-      *elemList << uid;
-      *elemList << idx;
-      *elemList << timeMs;
+      ptr->write(uid);
+      ptr->write(idx);
+      ptr->write(timeMs);
       ptr->init();
       data->push_back(ptr);
       VLOG("uid %lld, freq idx %d, sys time %lld", (long long)uid, idx, (long long)timeMs);
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
index b84b877..e0572dc 100644
--- a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
@@ -66,10 +66,9 @@
     uint64_t sysTimeMs = std::stoull(pch);
 
     auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_PULLED, timestamp);
-    auto elemList = ptr->GetAndroidLogEventList();
-    *elemList << uid;
-    *elemList << userTimeMs;
-    *elemList << sysTimeMs;
+    ptr->write(uid);
+    ptr->write(userTimeMs);
+    ptr->write(sysTimeMs);
     ptr->init();
     data->push_back(ptr);
     VLOG("uid %lld, user time %lld, sys time %lld", (long long)uid, (long long)userTimeMs, (long long)sysTimeMs);
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
index 319feef4..3ee636d 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
@@ -93,11 +93,10 @@
 
                     auto statePtr = make_shared<LogEvent>(
                             android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, timestamp);
-                    auto elemList = statePtr->GetAndroidLogEventList();
-                    *elemList << state.name;
-                    *elemList << state.residencyInMsecSinceBoot;
-                    *elemList << state.totalTransitions;
-                    *elemList << state.supportedOnlyInSuspend;
+                    statePtr->write(state.name);
+                    statePtr->write(state.residencyInMsecSinceBoot);
+                    statePtr->write(state.totalTransitions);
+                    statePtr->write(state.supportedOnlyInSuspend);
                     statePtr->init();
                     data->push_back(statePtr);
                     VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
@@ -106,11 +105,10 @@
                     for (auto voter : state.voters) {
                         auto voterPtr =
                                 make_shared<LogEvent>(android::util::POWER_STATE_VOTER_PULLED, timestamp);
-                        auto elemList = voterPtr->GetAndroidLogEventList();
-                        *elemList << state.name;
-                        *elemList << voter.name;
-                        *elemList << voter.totalTimeInMsecVotedForSinceBoot;
-                        *elemList << voter.totalNumberOfTimesVotedSinceBoot;
+                        voterPtr->write(state.name);
+                        voterPtr->write(voter.name);
+                        voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
+                        voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot);
                         voterPtr->init();
                         data->push_back(voterPtr);
                         VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
@@ -141,13 +139,12 @@
                                 const PowerStateSubsystemSleepState& state = subsystem.states[j];
                                 auto subsystemStatePtr = make_shared<LogEvent>(
                                         android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, timestamp);
-                                auto elemList = subsystemStatePtr->GetAndroidLogEventList();
-                                *elemList << subsystem.name;
-                                *elemList << state.name;
-                                *elemList << state.residencyInMsecSinceBoot;
-                                *elemList << state.totalTransitions;
-                                *elemList << state.lastEntryTimestampMs;
-                                *elemList << state.supportedOnlyInSuspend;
+                                subsystemStatePtr->write(subsystem.name);
+                                subsystemStatePtr->write(state.name);
+                                subsystemStatePtr->write(state.residencyInMsecSinceBoot);
+                                subsystemStatePtr->write(state.totalTransitions);
+                                subsystemStatePtr->write(state.lastEntryTimestampMs);
+                                subsystemStatePtr->write(state.supportedOnlyInSuspend);
                                 subsystemStatePtr->init();
                                 data->push_back(subsystemStatePtr);
                                 VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 913b906..1032138 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -29,21 +29,71 @@
 using std::string;
 using android::util::ProtoOutputStream;
 
-// 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) {
-    init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
+LogEvent::LogEvent(log_msg& msg) {
+    mContext =
+            create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t));
+    mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec;
+    init(mContext);
 }
 
-LogEvent::LogEvent(int tag, uint64_t timestampNs) : mList(tag), mTimestampNs(timestampNs) {
-}
-
-LogEvent::~LogEvent() {
+LogEvent::LogEvent(int32_t tagId, uint64_t timestampNs) {
+    mTimestampNs = timestampNs;
+    mTagId = tagId;
+    mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
+    if (mContext) {
+        android_log_write_int32(mContext, tagId);
+    }
 }
 
 void LogEvent::init() {
-    mList.convert_to_reader();
-    init(mTimestampNs, &mList);
+    if (mContext) {
+        const char* buffer;
+        size_t len = android_log_write_list_buffer(mContext, &buffer);
+        // turns to reader mode
+        mContext = create_android_log_parser(buffer, len);
+        init(mContext);
+    }
+}
+
+bool LogEvent::write(int32_t value) {
+    if (mContext) {
+        return android_log_write_int32(mContext, value) >= 0;
+    }
+    return false;
+}
+
+bool LogEvent::write(uint32_t value) {
+    if (mContext) {
+        return android_log_write_int32(mContext, value) >= 0;
+    }
+    return false;
+}
+
+bool LogEvent::write(uint64_t value) {
+    if (mContext) {
+        return android_log_write_int64(mContext, value) >= 0;
+    }
+    return false;
+}
+
+bool LogEvent::write(const string& value) {
+    if (mContext) {
+        return android_log_write_string8_len(mContext, value.c_str(), value.length()) >= 0;
+    }
+    return false;
+}
+
+bool LogEvent::write(float value) {
+    if (mContext) {
+        return android_log_write_float32(mContext, value) >= 0;
+    }
+    return false;
+}
+
+LogEvent::~LogEvent() {
+    if (mContext) {
+        android_log_destroy(&mContext);
+    }
 }
 
 /**
@@ -51,22 +101,25 @@
  * The goal is to do as little preprocessing as possible, because we read a tiny fraction
  * of the elements that are written to the log.
  */
-void LogEvent::init(int64_t timestampNs, android_log_event_list* reader) {
-    mTimestampNs = timestampNs;
-    mTagId = reader->tag();
-
+void LogEvent::init(android_log_context context) {
     mElements.clear();
     android_log_list_element elem;
-
     // TODO: The log is actually structured inside one list.  This is convenient
     // because we'll be able to use it to put the attribution (WorkSource) block first
     // without doing our own tagging scheme.  Until that change is in, just drop the
     // list-related log elements and the order we get there is our index-keyed data
     // structure.
+    int i = 0;
     do {
-        elem = android_log_read_next(reader->context());
+        elem = android_log_read_next(context);
         switch ((int)elem.type) {
             case EVENT_TYPE_INT:
+                // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. If we add WorkSource, it would
+                // be the list starting at [2].
+                if (i == 1) {
+                    mTagId = elem.data.int32;
+                    break;
+                }
             case EVENT_TYPE_FLOAT:
             case EVENT_TYPE_STRING:
             case EVENT_TYPE_LONG:
@@ -81,13 +134,10 @@
             default:
                 break;
         }
+        i++;
     } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
 }
 
-android_log_event_list* LogEvent::GetAndroidLogEventList() {
-    return &mList;
-}
-
 int64_t LogEvent::GetLong(size_t key, status_t* err) const {
     if (key < 1 || (key - 1)  >= mElements.size()) {
         *err = BAD_INDEX;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 2984940..7e8a96b 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -21,6 +21,7 @@
 #include <android/util/ProtoOutputStream.h>
 #include <log/log_event_list.h>
 #include <log/log_read.h>
+#include <private/android_logger.h>
 #include <utils/Errors.h>
 
 #include <memory>
@@ -45,12 +46,9 @@
     explicit LogEvent(log_msg& msg);
 
     /**
-     * Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
-     * mode. Obtain this list with the getter. Make sure to call init() before attempting to read
-     * any of the values. This constructor is useful for unit-testing since we can't pass in an
-     * android_log_event_list since there is no copy constructor or assignment operator available.
+     * Constructs a LogEvent with synthetic data for testing. Must call init() before reading.
      */
-    explicit LogEvent(int tag, uint64_t timestampNs);
+    explicit LogEvent(int32_t tagId, uint64_t timestampNs);
 
     ~LogEvent();
 
@@ -76,6 +74,17 @@
     float GetFloat(size_t key, status_t* err) const;
 
     /**
+     * Write test data to the LogEvent. This can only be used when the LogEvent is constructed
+     * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it.
+     */
+    bool write(uint32_t value);
+    bool write(int32_t value);
+    bool write(uint64_t value);
+    bool write(int64_t value);
+    bool write(const string& value);
+    bool write(float value);
+
+    /**
      * Return a string representation of this event.
      */
     string ToString() const;
@@ -91,13 +100,6 @@
     KeyValuePair GetKeyValueProto(size_t key) const;
 
     /**
-     * A pointer to the contained log_event_list.
-     *
-     * @return The android_log_event_list contained within.
-     */
-    android_log_event_list* GetAndroidLogEventList();
-
-    /**
      * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
      * and prepares the list for reading.
      */
@@ -113,16 +115,11 @@
     /**
      * Parses a log_msg into a LogEvent object.
      */
-    void init(const log_msg& msg);
-
-    /**
-     * Parses a log_msg into a LogEvent object.
-     */
-    void init(int64_t timestampNs, android_log_event_list* reader);
+    void init(android_log_context context);
 
     vector<android_log_list_element> mElements;
-    // Need a copy of the android_log_event_list so the strings are not cleared.
-    android_log_event_list mList;
+
+    android_log_context mContext;
 
     uint64_t mTimestampNs;
 
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 1c699e8..f7352cd 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -84,6 +84,9 @@
                 }
             }
             break;
+        case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED:
+            matched = false;
+            break;
     }
     return matched;
 }
@@ -165,15 +168,15 @@
                    matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
             // Float fields
             status_t err = NO_ERROR;
-            bool val = event.GetFloat(key, &err);
+            float val = event.GetFloat(key, &err);
             if (err == NO_ERROR) {
                 if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
-                    if (!(cur.lt_float() <= val)) {
+                    if (!(val < cur.lt_float())) {
                         allMatched = false;
                         break;
                     }
                 } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
-                    if (!(cur.gt_float() >= val)) {
+                    if (!(val > cur.gt_float())) {
                         allMatched = false;
                         break;
                     }
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index f9da68e..d47bd4f 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,7 +17,6 @@
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
-#include "../anomaly/DiscreteAnomalyTracker.h"
 #include "CountMetricProducer.h"
 #include "stats_util.h"
 
@@ -114,7 +113,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.
-    flushCounterIfNeeded(endTime);
+    flushIfNeeded(endTime);
     VLOG("metric %s dump report now...", mMetric.name().c_str());
 
     for (const auto& counter : mPastBuckets) {
@@ -170,7 +169,6 @@
 
     startNewProtoOutputStream(endTime);
     mPastBuckets.clear();
-    mByteSize = 0;
 
     return buffer;
 
@@ -188,7 +186,7 @@
         const LogEvent& event, bool scheduledPull) {
     uint64_t eventTimeNs = event.GetTimestampNs();
 
-    flushCounterIfNeeded(eventTimeNs);
+    flushIfNeeded(eventTimeNs);
 
     if (condition == false) {
         return;
@@ -205,41 +203,41 @@
         count++;
     }
 
-    VLOG("metric %s %s->%d", mMetric.name().c_str(), eventKey.c_str(),
-         (*mCurrentSlicedCounter)[eventKey]);
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
+                                         mCurrentSlicedCounter->find(eventKey)->second);
+    }
+
+    VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(),
+         (long long)(*mCurrentSlicedCounter)[eventKey]);
 }
 
 // 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 new bucket.
-void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
-    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
+    if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return;
     }
 
-    // adjust the bucket start time
-    // TODO: This (and addPastBucket to which it goes) doesn't really need to be an int64.
-    uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
     CountBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.mBucketNum = mCurrentBucketNum;
     for (const auto& counter : *mCurrentSlicedCounter) {
         info.mCount = counter.second;
         auto& bucketList = mPastBuckets[counter.first];
         bucketList.push_back(info);
-        VLOG("metric %s, dump key value: %s -> %d", mMetric.name().c_str(), counter.first.c_str(),
-             counter.second);
-        mByteSize += sizeof(info);
+        VLOG("metric %s, dump key value: %s -> %lld", mMetric.name().c_str(), counter.first.c_str(),
+             (long long)counter.second);
     }
 
     for (auto& tracker : mAnomalyTrackers) {
-        tracker->addOrUpdateBucket(mCurrentSlicedCounter, mCurrentBucketNum);
-        tracker->declareAndDeclareAnomaly();
+        tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
     }
 
     // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
     mCurrentSlicedCounter = std::make_shared<DimToValMap>();
-
+    uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
     mCurrentBucketNum += numBucketsForward;
     VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
@@ -250,7 +248,11 @@
 // greater than actual data size as it contains each dimension of
 // CountMetricData is  duplicated.
 size_t CountMetricProducer::byteSize() {
-    return mByteSize;
+    size_t totalSize = 0;
+    for (const auto& pair : mPastBuckets) {
+        totalSize += pair.second.size() * kBucketSize;
+    }
+    return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index b7e480c..b3f8ee3 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -21,9 +21,9 @@
 
 #include <android/util/ProtoOutputStream.h>
 #include <gtest/gtest_prod.h>
+#include "../anomaly/AnomalyTracker.h"
 #include "../condition/ConditionTracker.h"
 #include "../matchers/matcher_util.h"
-#include "../anomaly/DiscreteAnomalyTracker.h"
 #include "MetricProducer.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "stats_util.h"
@@ -36,6 +36,7 @@
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     int64_t mCount;
+    uint64_t mBucketNum;
 };
 
 class CountMetricProducer : public MetricProducer {
@@ -50,6 +51,8 @@
 
     void finish() override;
 
+    void flushIfNeeded(const uint64_t newEventTime) override;
+
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
 
@@ -76,18 +79,15 @@
     // TODO: Add a lock to mPastBuckets.
     std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
 
-    size_t mByteSize;
-
     // The current bucket.
     std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
 
-    vector<std::unique_ptr<DiscreteAnomalyTracker>> mAnomalyTrackers;
-
-    void flushCounterIfNeeded(const uint64_t newEventTime);
+    static const size_t kBucketSize = sizeof(CountBucket{});
 
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
+    FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index de6f365..b0a97b1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -63,6 +63,7 @@
 DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
+                                               const bool nesting,
                                                const sp<ConditionWizard>& wizard,
                                                const vector<KeyMatcher>& internalDimension,
                                                const uint64_t startTimeNs)
@@ -71,6 +72,7 @@
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
       mStopAllIndex(stopAllIndex),
+      mNested(nesting),
       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
@@ -108,16 +110,16 @@
 }
 
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
-        vector<DurationBucket>& 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);
+        const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) {
+    switch (mMetric.aggregation_type()) {
+        case DurationMetric_AggregationType_SUM:
+            return make_unique<OringDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
+                                                     mNested, mCurrentBucketStartTimeNs,
+                                                     mBucketSizeNs, mAnomalyTrackers, bucket);
+        case DurationMetric_AggregationType_MAX_SPARSE:
+            return make_unique<MaxDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
+                                                   mNested, mCurrentBucketStartTimeNs,
+                                                   mBucketSizeNs, mAnomalyTrackers, bucket);
     }
 }
 
@@ -129,7 +131,6 @@
 void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
     // Now for each of the on-going event, check if the condition has changed for them.
-    flushIfNeeded(eventTime);
     for (auto& pair : mCurrentSlicedDuration) {
         pair.second->onSlicedConditionMayChange(eventTime);
     }
@@ -140,27 +141,11 @@
     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) {
         pair.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
-static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
-                                       const vector<KeyValuePair>& key,
-                                       const vector<DurationBucketInfo>& buckets) {
-    DurationMetricData* 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] duration(ns): %lld", bucket.start_bucket_nanos(),
-             bucket.end_bucket_nanos(), bucket.duration_nanos());
-    }
-}
-
 std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
     long long endTime = time(nullptr) * NS_PER_SEC;
 
@@ -220,10 +205,8 @@
                   (long long)mCurrentBucketStartTimeNs);
 
     std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
-
     startNewProtoOutputStream(endTime);
-    mPastBuckets.clear();
-
+    // TODO: Properly clear the old buckets.
     return buffer;
 }
 
@@ -231,7 +214,6 @@
     if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
         return;
     }
-
     VLOG("flushing...........");
     for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) {
         if (it->second->flushIfNeeded(eventTime)) {
@@ -244,6 +226,7 @@
 
     int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
 }
 
 void DurationMetricProducer::onMatchedLogEventInternal(
@@ -262,7 +245,7 @@
     HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
 
     if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
-        mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
+        mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]);
     }
 
     auto it = mCurrentSlicedDuration.find(eventKey);
@@ -270,7 +253,7 @@
     if (matcherIndex == mStartIndex) {
         it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
     } else if (matcherIndex == mStopIndex) {
-        it->second->noteStop(atomKey, event.GetTimestampNs());
+        it->second->noteStop(atomKey, event.GetTimestampNs(), false);
     }
 }
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index eea00454..4c8dbcb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef DURATION_METRIC_PRODUCER_H
-#define DURATION_METRIC_PRODUCER_H
+#pragma once
+
 
 #include <unordered_map>
 
@@ -39,7 +39,8 @@
 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 bool nesting,
+                           const sp<ConditionWizard>& wizard,
                            const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
 
     virtual ~DurationMetricProducer();
@@ -47,6 +48,7 @@
     void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t newEventTime) override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -80,6 +82,9 @@
     // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
     const size_t mStopAllIndex;
 
+    // nest counting -- for the same key, stops must match the number of starts to make real stop
+    const bool mNested;
+
     // The dimension from the atom predicate. e.g., uid, wakelock name.
     const vector<KeyMatcher> mInternalDimension;
 
@@ -91,11 +96,8 @@
     std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
             mCurrentSlicedDuration;
 
-    void flushDurationIfNeeded(const uint64_t newEventTime);
-
-    std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucket>& bucket);
-
-    void flushIfNeeded(uint64_t timestamp);
+    std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey,
+                                                           std::vector<DurationBucket>& bucket);
 
     static const size_t kBucketSize = sizeof(DurationBucket{});
 };
@@ -104,4 +106,3 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 74ba40b..9a94a0e 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -96,7 +96,6 @@
     std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
 
     startNewProtoOutputStream(endTime);
-    mByteSize = 0;
 
     return buffer;
 }
@@ -121,11 +120,10 @@
     event.ToProto(*mProto);
     mProto->end(eventToken);
     mProto->end(wrapperToken);
-    // TODO: Find a proper way to derive the size of incoming LogEvent.
 }
 
 size_t EventMetricProducer::byteSize() {
-    return mByteSize;
+    return mProto->bytesWritten();
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 7740621..0c453cd 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -47,6 +47,8 @@
     void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t newEventTime) override {
+    }
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -65,8 +67,6 @@
 
 private:
     const EventMetric mMetric;
-
-    size_t mByteSize;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index ed18f89..42ac1a2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -63,8 +63,9 @@
 const int FIELD_ID_GAUGE = 3;
 
 GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard, const int pullTagId)
-    : MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
+                                         const sp<ConditionWizard>& wizard, const int pullTagId,
+                                         const int64_t startTimeNs)
+    : MetricProducer(startTimeNs, conditionIndex, wizard),
       mMetric(metric),
       mPullTagId(pullTagId) {
     if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
@@ -114,7 +115,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.
-    flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
+    flushIfNeeded(time(nullptr) * NS_PER_SEC);
 
     for (const auto& pair : mPastBuckets) {
         const HashableDimensionKey& hashableKey = pair.first;
@@ -168,7 +169,6 @@
 
     startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
     mPastBuckets.clear();
-    mByteSize = 0;
 
     return buffer;
 
@@ -178,15 +178,18 @@
 void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
     AutoMutex _l(mLock);
     VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
+    flushIfNeeded(eventTime);
     mCondition = conditionMet;
 
-    // Push mode. Nothing to do.
+    // Push mode. No need to proactively pull the gauge data.
     if (mPullTagId == -1) {
         return;
     }
-    // If (1) the condition is not met or (2) we already pulled the gauge metric in the current
-    // bucket, do not pull gauge again.
-    if (!mCondition || mCurrentSlicedBucket.size() > 0) {
+    if (!mCondition) {
+        return;
+    }
+    // Already have gauge metric for the current bucket, do not do it again.
+    if (mCurrentSlicedBucket->size() > 0) {
         return;
     }
     vector<std::shared_ptr<LogEvent>> allData;
@@ -197,16 +200,16 @@
     for (const auto& data : allData) {
         onMatchedLogEvent(0, *data, false /*scheduledPull*/);
     }
-    flushGaugeIfNeededLocked(eventTime);
+    flushIfNeeded(eventTime);
 }
 
 void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
     VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
 }
 
-long GaugeMetricProducer::getGauge(const LogEvent& event) {
+int64_t GaugeMetricProducer::getGauge(const LogEvent& event) {
     status_t err = NO_ERROR;
-    long val = event.GetLong(mMetric.gauge_field(), &err);
+    int64_t val = event.GetLong(mMetric.gauge_field(), &err);
     if (err == NO_ERROR) {
         return val;
     } else {
@@ -217,14 +220,9 @@
 
 void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
     AutoMutex mutex(mLock);
-    if (allData.size() == 0) {
-        return;
-    }
     for (const auto& data : allData) {
         onMatchedLogEvent(0, *data, true /*scheduledPull*/);
     }
-    uint64_t eventTime = allData.at(0)->GetTimestampNs();
-    flushGaugeIfNeededLocked(eventTime);
 }
 
 void GaugeMetricProducer::onMatchedLogEventInternal(
@@ -241,15 +239,21 @@
         return;
     }
 
-    // For gauge metric, we just simply use the latest guage in the given bucket.
-    const long gauge = getGauge(event);
-    if (gauge < 0) {
-        VLOG("Invalid gauge at event Time: %lld", (long long)eventTimeNs);
+    // When the event happens in a new bucket, flush the old buckets.
+    if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) {
+        flushIfNeeded(eventTimeNs);
+    }
+
+    // For gauge metric, we just simply use the first guage in the given bucket.
+    if (!mCurrentSlicedBucket->empty()) {
         return;
     }
-    mCurrentSlicedBucket[eventKey] = gauge;
-    if (mPullTagId < 0) {
-        flushGaugeIfNeededLocked(eventTimeNs);
+    const long gauge = getGauge(event);
+    if (gauge >= 0) {
+        (*mCurrentSlicedBucket)[eventKey] = gauge;
+    }
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge);
     }
 }
 
@@ -258,39 +262,45 @@
 // bucket.
 // if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
 // the GaugeMetricProducer while holding the lock.
-void GaugeMetricProducer::flushGaugeIfNeededLocked(const uint64_t eventTimeNs) {
-    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
-        VLOG("event time is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
-             (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
+    if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return;
     }
 
-    // Adjusts the bucket start time
-    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
     GaugeBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.mBucketNum = mCurrentBucketNum;
 
-    for (const auto& slice : mCurrentSlicedBucket) {
+    for (const auto& slice : *mCurrentSlicedBucket) {
         info.mGauge = slice.second;
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        mByteSize += sizeof(info);
-
-        VLOG("gauge metric %s, dump key value: %s -> %ld", mMetric.name().c_str(),
-             slice.first.c_str(), slice.second);
+        VLOG("gauge metric %s, dump key value: %s -> %lld", mMetric.name().c_str(),
+             slice.first.c_str(), (long long)slice.second);
     }
-    // Reset counters
-    mCurrentSlicedBucket.clear();
 
+    // Reset counters
+    for (auto& tracker : mAnomalyTrackers) {
+        tracker->addPastBucket(mCurrentSlicedBucket, mCurrentBucketNum);
+    }
+
+    mCurrentSlicedBucket = std::make_shared<DimToValMap>();
+
+    // Adjusts the bucket start time
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
     mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
     VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
          (long long)mCurrentBucketStartTimeNs);
 }
 
 size_t GaugeMetricProducer::byteSize() {
-    return mByteSize;
+    size_t totalSize = 0;
+    for (const auto& pair : mPastBuckets) {
+        totalSize += pair.second.size() * kBucketSize;
+    }
+    return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index f9e4deb..930afb2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -19,6 +19,7 @@
 #include <unordered_map>
 
 #include <android/util/ProtoOutputStream.h>
+#include <gtest/gtest_prod.h>
 #include "../condition/ConditionTracker.h"
 #include "../external/PullDataReceiver.h"
 #include "../external/StatsPullerManager.h"
@@ -35,6 +36,7 @@
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     int64_t mGauge;
+    uint64_t mBucketNum;
 };
 
 // This gauge metric producer first register the puller to automatically pull the gauge at the
@@ -46,7 +48,8 @@
     // TODO: Pass in the start time from MetricsManager, it should be consistent
     // for all metrics.
     GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
-                        const sp<ConditionWizard>& wizard, const int pullTagId);
+                        const sp<ConditionWizard>& wizard, const int pullTagId,
+                        const int64_t startTimeNs);
 
     virtual ~GaugeMetricProducer();
 
@@ -57,6 +60,7 @@
     void onSlicedConditionMayChange(const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t newEventTime) override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -92,13 +96,15 @@
     std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
 
     // The current bucket.
-    std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
+    std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>();
 
-    void flushGaugeIfNeededLocked(const uint64_t newEventTime);
+    int64_t getGauge(const LogEvent& event);
 
-    long getGauge(const LogEvent& event);
+    static const size_t kBucketSize = sizeof(GaugeBucket{});
 
-    size_t mByteSize;
+    FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
+    FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition);
+    FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index c0930e3..0f93744 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,6 +17,7 @@
 #ifndef METRIC_PRODUCER_H
 #define METRIC_PRODUCER_H
 
+#include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
@@ -58,17 +59,29 @@
     // 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.
     virtual void finish() = 0;
+    virtual void flushIfNeeded(const uint64_t newEventTime) = 0;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport and update all its
     // implementations.
+    // onDumpReport returns the proto-serialized output and clears the previously stored contents.
     virtual std::unique_ptr<std::vector<uint8_t>> onDumpReport() = 0;
 
     virtual bool isConditionSliced() const {
         return mConditionSliced;
     };
 
+    // Returns the memory in bytes currently used to store this metric's data. Does not change
+    // state.
     virtual size_t byteSize() = 0;
 
+    void addAnomalyTracker(sp<AnomalyTracker> tracker) {
+        mAnomalyTrackers.push_back(tracker);
+    }
+
+    int64_t getBuckeSizeInNs() const {
+        return mBucketSizeNs;
+    }
+
 protected:
     const uint64_t mStartTimeNs;
 
@@ -94,6 +107,8 @@
 
     std::vector<EventConditionLink> mConditionLinks;
 
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+
     /*
      * Individual metrics can implement their own business logic here. All pre-processing is done.
      *
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index e8a862f..5916b040 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -38,8 +38,8 @@
 
 MetricsManager::MetricsManager(const StatsdConfig& config) {
     mConfigValid = initStatsdConfig(config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
-                                    mAllMetricProducers, mConditionToMetricMap, mTrackerToMetricMap,
-                                    mTrackerToConditionMap);
+                                    mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
+                                    mTrackerToMetricMap, mTrackerToConditionMap);
 }
 
 MetricsManager::~MetricsManager() {
@@ -150,6 +150,19 @@
     }
 }
 
+void MetricsManager::onAnomalyAlarmFired(const uint64_t timestampNs,
+                                         sp<const AnomalyAlarm> anomaly) {
+    for (const auto& itr : mAllAnomalyTrackers) {
+        itr->declareAnomaly(timestampNs);
+    }
+}
+
+void MetricsManager::setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+    for (auto& itr : mAllAnomalyTrackers) {
+        itr->setAnomalyMonitor(anomalyMonitor);
+    }
+}
+
 // Returns the total byte size of all metrics managed by a single config source.
 size_t MetricsManager::byteSize() {
     size_t totalSize = 0;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 39c79f9..59ade7c 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "logd/LogEvent.h"
@@ -43,9 +45,15 @@
     // Called when everything should wrap up. We are about to finish (e.g., new config comes).
     void finish();
 
+    void onAnomalyAlarmFired(const uint64_t timestampNs, sp<const AnomalyAlarm> anomaly);
+
+    void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
+
     // Config source owner can call onDumpReport() to get all the metrics collected.
     std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
 
+    // Computes the total byte size of all metrics managed by a single config source.
+    // Does not change the state.
     size_t byteSize();
 
 private:
@@ -68,6 +76,9 @@
     // Hold all metrics from the config.
     std::vector<sp<MetricProducer>> mAllMetricProducers;
 
+    // Hold all alert trackers.
+    std::vector<sp<AnomalyTracker>> mAllAnomalyTrackers;
+
     // To make the log processing more efficient, we want to do as much filtering as possible
     // before we go into individual trackers and conditions to match.
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index a35070b..9cbe6f6 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -188,7 +188,6 @@
 
     startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
     mPastBuckets.clear();
-    mByteSize = 0;
 
     return buffer;
 
@@ -215,7 +214,7 @@
             for (const auto& data : allData) {
                 onMatchedLogEvent(0, *data, false);
             }
-            flush_if_needed(eventTime);
+            flushIfNeeded(eventTime);
         }
         return;
     }
@@ -230,12 +229,12 @@
         uint64_t eventTime = allData.at(0)->GetTimestampNs();
         // alarm is not accurate and might drift.
         if (eventTime > mCurrentBucketStartTimeNs + mBucketSizeNs * 3 / 2) {
-            flush_if_needed(eventTime);
+            flushIfNeeded(eventTime);
         }
         for (const auto& data : allData) {
             onMatchedLogEvent(0, *data, true);
         }
-        flush_if_needed(eventTime);
+        flushIfNeeded(eventTime);
     }
 }
 
@@ -282,7 +281,7 @@
             }
         }
     } else {
-        flush_if_needed(eventTimeNs);
+        flushIfNeeded(eventTimeNs);
         interval.raw.push_back(make_pair(value, 0));
     }
 }
@@ -298,18 +297,18 @@
     }
 }
 
-void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
+void ValueMetricProducer::flushIfNeeded(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());
     ValueBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.mBucketNum = mCurrentBucketNum;
 
     int tainted = 0;
     for (const auto& slice : mCurrentSlicedBucket) {
@@ -329,23 +328,29 @@
         // it will auto create new vector of ValuebucketInfo if the key is not found.
         auto& bucketList = mPastBuckets[slice.first];
         bucketList.push_back(info);
-        mByteSize += sizeof(info);
     }
 
     // Reset counters
     mCurrentSlicedBucket.swap(mNextSlicedBucket);
     mNextSlicedBucket.clear();
+
     int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
+
     if (numBucketsForward > 1) {
         VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
     }
-    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
     VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
          (long long)mCurrentBucketStartTimeNs);
 }
 
 size_t ValueMetricProducer::byteSize() {
-    return mByteSize;
+    size_t totalSize = 0;
+    for (const auto& pair : mPastBuckets) {
+        totalSize += pair.second.size() * kBucketSize;
+    }
+    return totalSize;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index c6c87f5..2b0b0ad 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -33,6 +33,7 @@
     int64_t mBucketStartNs;
     int64_t mBucketEndNs;
     int64_t mValue;
+    uint64_t mBucketNum;
 };
 
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
@@ -46,6 +47,7 @@
     void onConditionChanged(const bool condition, const uint64_t eventTime) override;
 
     void finish() override;
+    void flushIfNeeded(const uint64_t eventTimeNs) override;
 
     // TODO: Pass a timestamp as a parameter in onDumpReport.
     std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
@@ -102,9 +104,7 @@
 
     long get_value(const LogEvent& event);
 
-    void flush_if_needed(const uint64_t eventTimeNs);
-
-    size_t mByteSize;
+    static const size_t kBucketSize = sizeof(ValueBucket{});
 
     FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 5c76d0e..c91ea0f 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -17,6 +17,7 @@
 #ifndef DURATION_TRACKER_H
 #define DURATION_TRACKER_H
 
+#include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
 #include "stats_util.h"
 
@@ -34,6 +35,10 @@
 // Hold duration information for one atom level duration in current on-going bucket.
 struct DurationInfo {
     DurationState state;
+
+    // the number of starts seen.
+    int32_t startCount;
+
     // most recent start time.
     int64_t lastStartTime;
     // existing duration in current bucket.
@@ -42,29 +47,37 @@
     // cache the HashableDimensionKeys we need to query the condition for this duration event.
     ConditionKey conditionKeys;
 
-    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+    DurationInfo() : state(kStopped), startCount(0), lastStartTime(0), lastDuration(0){};
 };
 
 struct DurationBucket {
-    int64_t mBucketStartNs;
-    int64_t mBucketEndNs;
-    int64_t mDuration;
+    uint64_t mBucketStartNs;
+    uint64_t mBucketEndNs;
+    uint64_t mDuration;
+    uint64_t mBucketNum;
 };
 
 class DurationTracker {
 public:
-    DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, uint64_t currentBucketStartNs,
-                    uint64_t bucketSizeNs, std::vector<DurationBucket>& bucket)
-        : mWizard(wizard),
+    DurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                    int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+                    uint64_t bucketSizeNs, const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
+                    std::vector<DurationBucket>& bucket)
+        : mEventKey(eventKey),
+          mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
-          mCurrentBucketStartTimeNs(currentBucketStartNs),
           mBucketSizeNs(bucketSizeNs),
+          mNested(nesting),
+          mCurrentBucketStartTimeNs(currentBucketStartNs),
           mBucket(bucket),
-          mDuration(0){};
+          mDuration(0),
+          mCurrentBucketNum(0),
+          mAnomalyTrackers(anomalyTrackers){};
     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 noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                          const bool stopAll) = 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;
@@ -72,18 +85,78 @@
     // events, so that the owner can safely remove the tracker.
     virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
 
+    // Predict the anomaly timestamp given the current status.
+    virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                              const uint64_t currentTimestamp) const = 0;
+
 protected:
+    // Starts the anomaly alarm.
+    void startAnomalyAlarm(const uint64_t eventTime) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->startAlarm(mEventKey,
+                                           predictAnomalyTimestampNs(*anomalyTracker, eventTime));
+            }
+        }
+    }
+
+    // Stops the anomaly alarm.
+    void stopAnomalyAlarm() {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->stopAlarm(mEventKey);
+            }
+        }
+    }
+
+    void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+            }
+        }
+    }
+
+    void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
+                                 const int64_t& currentBucketValue) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mEventKey,
+                                                        currentBucketValue);
+            }
+        }
+    }
+
+    void declareAnomalyIfAlarmExpired(const uint64_t& timestamp) {
+        for (auto& anomalyTracker : mAnomalyTrackers) {
+            if (anomalyTracker != nullptr) {
+                anomalyTracker->declareAnomalyIfAlarmExpired(mEventKey, timestamp);
+            }
+        }
+    }
+
+    HashableDimensionKey mEventKey;
+
     sp<ConditionWizard> mWizard;
 
-    int mConditionTrackerIndex;
+    const int mConditionTrackerIndex;
+
+    const int64_t mBucketSizeNs;
+
+    const bool mNested;
 
     uint64_t mCurrentBucketStartTimeNs;
 
-    int64_t mBucketSizeNs;
-
     std::vector<DurationBucket>& mBucket;  // where to write output
 
     int64_t mDuration;  // current recorded duration result
+
+    uint64_t mCurrentBucketNum;
+
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 43c21a8..06e743d 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -23,14 +23,18 @@
 namespace os {
 namespace statsd {
 
-MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+MaxDurationTracker::MaxDurationTracker(const HashableDimensionKey& eventKey,
+                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                                        std::vector<DurationBucket>& bucket)
-    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket) {
+    : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
+                      anomalyTrackers, bucket) {
 }
 
 void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
                                    const uint64_t eventTime, const ConditionKey& conditionKey) {
+    flushIfNeeded(eventTime);
     // this will construct a new DurationInfo if this key didn't exist.
     DurationInfo& duration = mInfos[key];
     duration.conditionKeys = conditionKey;
@@ -38,10 +42,10 @@
 
     switch (duration.state) {
         case kStarted:
-            // The same event is already started. Because we are not counting nesting, so ignore.
+            duration.startCount++;
             break;
         case kPaused:
-            // Safe to do nothing here. Paused means started but condition is false.
+            duration.startCount++;
             break;
         case kStopped:
             if (!condition) {
@@ -51,11 +55,16 @@
                 duration.state = DurationState::kStarted;
                 duration.lastStartTime = eventTime;
             }
+            duration.startCount = 1;
             break;
     }
 }
 
-void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+
+void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                                  bool forceStop) {
+    flushIfNeeded(eventTime);
+    declareAnomalyIfAlarmExpired(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.
@@ -68,31 +77,47 @@
             // 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);
+            duration.startCount--;
+            if (forceStop || !mNested || duration.startCount <= 0) {
+                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;
+                duration.lastStartTime = -1;
+                VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            }
             break;
         }
         case DurationState::kPaused: {
-            duration.state = DurationState::kStopped;
+            duration.startCount--;
+            if (forceStop || !mNested || duration.startCount <= 0) {
+                duration.state = DurationState::kStopped;
+            }
             break;
         }
     }
 
     if (duration.lastDuration > mDuration) {
         mDuration = duration.lastDuration;
+        detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
         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);
+    if (duration.state == DurationState::kStopped) {
+        mInfos.erase(key);
+    }
 }
+
 void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
-    for (auto& pair : mInfos) {
-        noteStop(pair.first, eventTime);
+    std::set<HashableDimensionKey> keys;
+    for (const auto& pair : mInfos) {
+        keys.insert(pair.first);
+    }
+    for (auto& key : keys) {
+        noteStop(key, eventTime, true);
     }
 }
 
@@ -111,7 +136,7 @@
     DurationBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
     info.mBucketEndNs = endTime;
-
+    info.mBucketNum = mCurrentBucketNum;
 
     uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
     mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
@@ -154,6 +179,7 @@
     if (mDuration != 0) {
         info.mDuration = mDuration;
         mBucket.push_back(info);
+        addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
         VLOG("  final duration for last bucket: %lld", (long long)mDuration);
     }
 
@@ -163,11 +189,15 @@
             DurationBucket info;
             info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
             info.mBucketEndNs = endTime + mBucketSizeNs * i;
+            info.mBucketNum = mCurrentBucketNum + i;
             info.mDuration = mBucketSizeNs;
             mBucket.push_back(info);
+            addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
             VLOG("  filling gap bucket with duration %lld", (long long)mBucketSizeNs);
         }
     }
+
+    mCurrentBucketNum += numBucketsForward;
     // If this tracker has no pending events, tell owner to remove.
     return !hasPendingEvent;
 }
@@ -193,6 +223,8 @@
 
 void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
                                               const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     auto it = mInfos.find(key);
     if (it == mInfos.end()) {
         return;
@@ -204,7 +236,6 @@
             if (!conditionMet) {
                 it->second.state = DurationState::kPaused;
                 it->second.lastDuration += (timestamp - it->second.lastStartTime);
-
                 VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
             }
             break;
@@ -221,6 +252,16 @@
             }
             break;
     }
+    if (it->second.lastDuration > mDuration) {
+        mDuration = it->second.lastDuration;
+        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+    }
+}
+
+int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                                      const uint64_t currentTimestamp) const {
+    ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
+    return currentTimestamp;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index b095884..ca10210 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,22 +28,34 @@
 // they stop or bucket expires.
 class MaxDurationTracker : public DurationTracker {
 public:
-    MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
-                       uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+    MaxDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                       int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+                       uint64_t bucketSizeNs,
+                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                        std::vector<DurationBucket>& bucket);
     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 noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                  const bool stopAll) 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;
 
+    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                      const uint64_t currentTimestamp) const override;
+
 private:
     std::map<HashableDimensionKey, DurationInfo> mInfos;
 
     void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
                               const uint64_t timestamp);
+
+    FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration);
+    FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
+    FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
+    FRIEND_TEST(MaxDurationTrackerTest, TestStopAll);
+    FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index e4f1d21..29b6c89 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -20,10 +20,17 @@
 namespace android {
 namespace os {
 namespace statsd {
-OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
-                                           uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+
+using std::pair;
+
+OringDurationTracker::OringDurationTracker(const HashableDimensionKey& eventKey,
+                                           sp<ConditionWizard> wizard, int conditionIndex,
+                                           bool nesting, uint64_t currentBucketStartNs,
+                                           uint64_t bucketSizeNs,
+                                           const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                                            std::vector<DurationBucket>& bucket)
-    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket),
+    : DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
+                      anomalyTrackers, bucket),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -31,14 +38,16 @@
 
 void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
                                      const uint64_t eventTime, const ConditionKey& conditionKey) {
+    flushIfNeeded(eventTime);
     if (condition) {
         if (mStarted.size() == 0) {
             mLastStartTime = eventTime;
             VLOG("record first start....");
+            startAnomalyAlarm(eventTime);
         }
-        mStarted.insert(key);
+        mStarted[key]++;
     } else {
-        mPaused.insert(key);
+        mPaused[key]++;
     }
 
     if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
@@ -48,69 +57,98 @@
     VLOG("Oring: %s start, condition %d", key.c_str(), condition);
 }
 
-void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp) {
+void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
+                                    const bool stopAll) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     VLOG("Oring: %s stop", key.c_str());
     auto it = mStarted.find(key);
     if (it != mStarted.end()) {
-        mStarted.erase(it);
+        (it->second)--;
+        if (stopAll || !mNested || it->second <= 0) {
+            mStarted.erase(it);
+            mConditionKeyMap.erase(key);
+        }
         if (mStarted.empty()) {
             mDuration += (timestamp - mLastStartTime);
+            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
+            mLastStartTime = -1;
             VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
                  (long long)mDuration);
         }
     }
 
-    mPaused.erase(key);
-    mConditionKeyMap.erase(key);
+    auto pausedIt = mPaused.find(key);
+    if (pausedIt != mPaused.end()) {
+        (pausedIt->second)--;
+        if (stopAll || !mNested || pausedIt->second <= 0) {
+            mPaused.erase(pausedIt);
+            mConditionKeyMap.erase(key);
+        }
+    }
+    if (mStarted.empty()) {
+        stopAnomalyAlarm();
+    }
 }
+
 void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     if (!mStarted.empty()) {
         mDuration += (timestamp - mLastStartTime);
         VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
              (long long)mDuration);
+        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
     }
 
+    stopAnomalyAlarm();
     mStarted.clear();
     mPaused.clear();
     mConditionKeyMap.clear();
+    mLastStartTime = -1;
 }
 
 bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
-    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+    if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
         return false;
     }
     VLOG("OringDurationTracker Flushing.............");
     // adjust the bucket start time
     int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-    DurationBucket info;
-    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
-    info.mBucketStartNs = mCurrentBucketStartTimeNs;
-    info.mBucketEndNs = endTime;
-
-    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
-    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
-
+    DurationBucket current_info;
+    current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+    current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
+    current_info.mBucketNum = mCurrentBucketNum;
+    // Process the current bucket.
     if (mStarted.size() > 0) {
-        mDuration += (endTime - mLastStartTime);
+        mDuration += (current_info.mBucketEndNs - mLastStartTime);
+        mLastStartTime = current_info.mBucketEndNs;
     }
-    if (mDuration != 0) {
-        info.mDuration = 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 (mDuration > 0) {
+        current_info.mDuration = mDuration;
+        mBucket.push_back(current_info);
+        addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
+        VLOG("  duration: %lld", (long long)current_info.mDuration);
     }
 
     if (mStarted.size() > 0) {
         for (int i = 1; i < numBucketsForward; i++) {
             DurationBucket info;
-            info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
-            info.mBucketEndNs = endTime + mBucketSizeNs * i;
+            info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
+            info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
+            info.mBucketNum = mCurrentBucketNum + i;
             info.mDuration = mBucketSizeNs;
-            mBucket.push_back(info);
-            VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
+            mLastStartTime = info.mBucketEndNs;
+            if (info.mDuration > 0) {
+                mBucket.push_back(info);
+                addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
+                VLOG("  add filling bucket with duration %lld", (long long)info.mDuration);
+            }
         }
     }
-    mLastStartTime = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+    mCurrentBucketNum += numBucketsForward;
+
     mDuration = 0;
 
     // if all stopped, then tell owner it's safe to remove this tracker.
@@ -118,11 +156,13 @@
 }
 
 void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
-    vector<HashableDimensionKey> startedToPaused;
-    vector<HashableDimensionKey> pausedToStarted;
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
+    vector<pair<HashableDimensionKey, int>> startedToPaused;
+    vector<pair<HashableDimensionKey, int>> pausedToStarted;
     if (!mStarted.empty()) {
         for (auto it = mStarted.begin(); it != mStarted.end();) {
-            auto key = *it;
+            const auto& key = it->first;
             if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
                 VLOG("Key %s dont have condition key", key.c_str());
                 ++it;
@@ -130,8 +170,8 @@
             }
             if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
                 ConditionState::kTrue) {
+                startedToPaused.push_back(*it);
                 it = mStarted.erase(it);
-                startedToPaused.push_back(key);
                 VLOG("Key %s started -> paused", key.c_str());
             } else {
                 ++it;
@@ -139,15 +179,17 @@
         }
 
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
+            mDuration = (timestamp - mLastStartTime);
+            mLastStartTime = -1;
             VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
                  (long long)mDuration);
+            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
         }
     }
 
     if (!mPaused.empty()) {
         for (auto it = mPaused.begin(); it != mPaused.end();) {
-            auto key = *it;
+            const auto& key = it->first;
             if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
                 VLOG("Key %s dont have condition key", key.c_str());
                 ++it;
@@ -155,8 +197,8 @@
             }
             if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
                 ConditionState::kTrue) {
+                pausedToStarted.push_back(*it);
                 it = mPaused.erase(it);
-                pausedToStarted.push_back(key);
                 VLOG("Key %s paused -> started", key.c_str());
             } else {
                 ++it;
@@ -168,26 +210,95 @@
         }
     }
 
+    if (mStarted.empty() && !pausedToStarted.empty()) {
+        startAnomalyAlarm(timestamp);
+    }
     mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
     mPaused.insert(startedToPaused.begin(), startedToPaused.end());
+
+    if (mStarted.empty()) {
+        stopAnomalyAlarm();
+    }
 }
 
 void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+    flushIfNeeded(timestamp);
+    declareAnomalyIfAlarmExpired(timestamp);
     if (condition) {
         if (!mPaused.empty()) {
             VLOG("Condition true, all started");
             if (mStarted.empty()) {
-                mLastStartTime = timestamp;
+                mLastStartTime = -1;
+            }
+            if (mStarted.empty() && !mPaused.empty()) {
+                startAnomalyAlarm(timestamp);
             }
             mStarted.insert(mPaused.begin(), mPaused.end());
+            mPaused.clear();
         }
     } else {
         if (!mStarted.empty()) {
             VLOG("Condition false, all paused");
-            mDuration += (timestamp - mLastStartTime);
+            mDuration = (timestamp - mLastStartTime);
+            mLastStartTime = -1;
             mPaused.insert(mStarted.begin(), mStarted.end());
+            mStarted.clear();
+            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
         }
     }
+    if (mStarted.empty()) {
+        stopAnomalyAlarm();
+    }
+}
+
+int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                                        const uint64_t eventTimestampNs) const {
+    // TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
+    // All variables below represent durations (not timestamps).
+
+    // The time until the current bucket ends. This is how much more 'space' it can hold.
+    const int64_t currRemainingBucketSizeNs =
+            mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
+    // TODO: This should never be < 0. Document/guard against possible failures if it is.
+
+    const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
+
+    // As we move into the future, old buckets get overwritten (so their old data is erased).
+
+    // Sum of past durations. Will change as we overwrite old buckets.
+    int64_t pastNs = mDuration;
+    pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
+
+    // How much of the threshold is still unaccounted after considering pastNs.
+    int64_t leftNs = thresholdNs - pastNs;
+
+    // First deal with the remainder of the current bucket.
+    if (leftNs <= currRemainingBucketSizeNs) {  // Predict the anomaly will occur in this bucket.
+        return eventTimestampNs + leftNs;
+    }
+    // The remainder of this bucket contributes, but we must then move to the next bucket.
+    pastNs += currRemainingBucketSizeNs;
+
+    // Now deal with the past buckets, starting with the oldest.
+    for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastPackets();
+         futBucketIdx++) {
+        // We now overwrite the oldest bucket with the previous 'current', and start a new
+        // 'current'.
+        pastNs -= anomalyTracker.getPastBucketValue(
+                mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastPackets() + futBucketIdx);
+        leftNs = thresholdNs - pastNs;
+        if (leftNs <= mBucketSizeNs) {  // Predict anomaly will occur in this bucket.
+            return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) +
+                   leftNs;
+        } else {  // This bucket would be entirely filled, and we'll need to move to the next
+                  // bucket.
+            pastNs += mBucketSizeNs;
+        }
+    }
+
+    // If we have reached this point, we even have to overwrite the the original current bucket.
+    // Thus, none of the past data will still be extant - pastNs is now 0.
+    return eventTimestampNs + thresholdNs;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index b54dafa..6f92113 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,27 +27,39 @@
 // 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,
+    OringDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
+                         int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+                         uint64_t bucketSizeNs,
+                         const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
                          std::vector<DurationBucket>& bucket);
     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 noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                  const bool stopAll) 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;
 
+    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                      const uint64_t currentTimestamp) const 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;
+    std::map<HashableDimensionKey, int> mStarted;
+    std::map<HashableDimensionKey, int> mPaused;
     int64_t mLastStartTime;
     std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
+
+    FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap);
+    FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
+    FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
+    FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+    FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 4c63b20..2344cb4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -190,7 +190,8 @@
                  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>>& trackerToMetricMap,
+                 unordered_map<string, int>& metricMap) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
                                 config.event_metric_size() + config.value_metric_size();
@@ -208,6 +209,7 @@
         }
 
         int metricIndex = allMetricProducers.size();
+        metricMap.insert({metric.name(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
                                          allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -236,6 +238,7 @@
     for (int i = 0; i < config.duration_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const DurationMetric& metric = config.duration_metric(i);
+        metricMap.insert({metric.name(), metricIndex});
 
         auto what_it = conditionTrackerMap.find(metric.what());
         if (what_it == conditionTrackerMap.end()) {
@@ -252,6 +255,8 @@
 
         const auto& simpleCondition = durationWhat.simple_condition();
 
+        bool nesting = simpleCondition.count_nesting();
+
         int trackerIndices[3] = {-1, -1, -1};
         if (!simpleCondition.has_start() ||
             !handleMetricWithLogTrackers(simpleCondition.start(), metricIndex,
@@ -294,7 +299,7 @@
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
-                wizard, internalDimension, startTimeNs);
+                nesting, wizard, internalDimension, startTimeNs);
 
         allMetricProducers.push_back(durationMetric);
     }
@@ -303,6 +308,7 @@
     for (int i = 0; i < config.event_metric_size(); i++) {
         int metricIndex = allMetricProducers.size();
         const EventMetric& metric = config.event_metric(i);
+        metricMap.insert({metric.name(), metricIndex});
         if (!metric.has_name() || !metric.has_what()) {
             ALOGW("cannot find the metric name or what in config");
             return false;
@@ -340,6 +346,7 @@
         }
 
         int metricIndex = allMetricProducers.size();
+        metricMap.insert({metric.name(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
                                          allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -385,6 +392,7 @@
         }
 
         int metricIndex = allMetricProducers.size();
+        metricMap.insert({metric.name(), metricIndex});
         int trackerIndex;
         if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
                                          allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
@@ -417,21 +425,43 @@
         }
 
         sp<MetricProducer> gaugeProducer =
-                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId);
+                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
         allMetricProducers.push_back(gaugeProducer);
     }
     return true;
 }
 
+bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap,
+                vector<sp<MetricProducer>>& allMetricProducers,
+                vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
+    for (int i = 0; i < config.alert_size(); i++) {
+        const Alert& alert = config.alert(i);
+        const auto& itr = metricProducerMap.find(alert.metric_name());
+        if (itr == metricProducerMap.end()) {
+            ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
+                  alert.metric_name().c_str());
+            return false;
+        }
+        const int metricIndex = itr->second;
+        sp<AnomalyTracker> anomalyTracker =
+                new AnomalyTracker(alert, allMetricProducers[metricIndex]->getBuckeSizeInNs());
+        allMetricProducers[metricIndex]->addAnomalyTracker(anomalyTracker);
+        allAnomalyTrackers.push_back(anomalyTracker);
+    }
+    return true;
+}
+
 bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
                       vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
+                      vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       unordered_map<int, std::vector<int>>& conditionToMetricMap,
                       unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       unordered_map<int, std::vector<int>>& trackerToConditionMap) {
     unordered_map<string, int> logTrackerMap;
     unordered_map<string, int> conditionTrackerMap;
+    unordered_map<string, int> metricProducerMap;
 
     if (!initLogTrackers(config, logTrackerMap, allLogEntryMatchers, allTagIds)) {
         ALOGE("initLogMatchingTrackers failed");
@@ -447,10 +477,14 @@
 
     if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
                      allConditionTrackers, allMetricProducers, conditionToMetricMap,
-                     trackerToMetricMap)) {
+                     trackerToMetricMap, metricProducerMap)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
+    if (!initAlerts(config, metricProducerMap, allMetricProducers, allAnomalyTrackers)) {
+        ALOGE("initAlerts failed");
+        return false;
+    }
     return true;
 }
 
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index edf3af0..7d7e0c3 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -88,6 +88,7 @@
                       std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
+                      vector<sp<AnomalyTracker>>& allAnomalyTrackers,
                       std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index e1d0aceb..b7d8f97 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -35,15 +35,14 @@
 
 typedef std::map<std::string, HashableDimensionKey> ConditionKey;
 
-// TODO: For P, change int to int64_t.
-// TODO: Should HashableDimensionKey be marked here as const?
-typedef std::unordered_map<HashableDimensionKey, int> DimToValMap;
+typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
 
 EventMetricData parse(log_msg msg);
 
 int getTagId(log_msg msg);
 
 std::string getHashableKey(std::vector<KeyValuePair> key);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 9a760b1..c8fa155 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -47,6 +47,7 @@
 }
 
 enum LogicalOperation {
+  LOGICAL_OPERATION_UNSPECIFIED = 0;
   AND = 1;
   OR = 2;
   NOT = 3;
@@ -87,7 +88,7 @@
     UNKNOWN = 0;
     FALSE = 1;
   }
-  optional InitialValue initial_value = 5 [default = UNKNOWN];
+  optional InitialValue initial_value = 5 [default = FALSE];
 
   repeated KeyMatcher dimension = 6;
 }
@@ -111,23 +112,12 @@
   optional int64 bucket_size_millis = 1;
 }
 
-message Alert {
-    optional string name = 1;
+message EventConditionLink {
+    optional string condition = 1;
 
-    optional string metric_name = 2;
+    repeated KeyMatcher key_in_main = 2;
 
-    message IncidentdDetails {
-        repeated int32 section = 1;
-  }
-  optional IncidentdDetails incidentd_details = 3;
-
-  optional int32 number_of_buckets = 4;
-
-  optional int32 refractory_period_secs = 5;
-
-  optional int64 trigger_if_sum_gt = 6;
-
-  optional int32 refractory_period_in_buckets = 7;
+    repeated KeyMatcher key_in_condition = 3;
 }
 
 message EventMetric {
@@ -151,9 +141,7 @@
 
     optional Bucket bucket = 5;
 
-    optional bool include_in_output = 6;
-
-    repeated EventConditionLink links = 7;
+    repeated EventConditionLink links = 6;
 }
 
 message DurationMetric {
@@ -166,11 +154,11 @@
     repeated EventConditionLink links = 4;
 
     enum AggregationType {
-        DURATION_SUM = 1;
+      SUM = 1;
 
-        DURATION_MAX_SPARSE = 2;
+      MAX_SPARSE = 2;
     }
-    optional AggregationType type = 5;
+    optional AggregationType aggregation_type = 5 [default = SUM];
 
     repeated KeyMatcher dimension = 6;
 
@@ -208,16 +196,28 @@
 
     repeated EventConditionLink links = 7;
 
-    enum Operation { SUM = 1; }
-    optional Operation operation = 9 [default = SUM];
+    enum AggregationType {
+      SUM = 1;
+    }
+    optional AggregationType aggregation_type = 8 [default = SUM];
 }
 
-message EventConditionLink {
-  optional string condition = 1;
+message Alert {
+    optional string name = 1;
 
-  repeated KeyMatcher key_in_main = 2;
-  repeated KeyMatcher key_in_condition = 3;
-};
+    optional string metric_name = 2;
+
+    message IncidentdDetails {
+      repeated int32 section = 1;
+    }
+    optional IncidentdDetails incidentd_details = 3;
+
+    optional int32 number_of_buckets = 4;
+
+    optional int32 refractory_period_secs = 5;
+
+    optional int64 trigger_if_sum_gt = 6;
+}
 
 message StatsdConfig {
     optional string name = 1;
@@ -236,5 +236,5 @@
 
     repeated Condition condition = 8;
 
-    repeated Alert alerts = 9;
+    repeated Alert alert = 9;
 }
diff --git a/cmds/statsd/tests/AnomalyMonitor_test.cpp b/cmds/statsd/tests/AnomalyMonitor_test.cpp
index 59fa160..920ca08 100644
--- a/cmds/statsd/tests/AnomalyMonitor_test.cpp
+++ b/cmds/statsd/tests/AnomalyMonitor_test.cpp
@@ -20,6 +20,8 @@
 
 #ifdef __ANDROID__
 TEST(AnomalyMonitor, popSoonerThan) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> set;
     AnomalyMonitor am(2);
 
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index fad5de6..f570522 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -85,7 +85,7 @@
         // TODO: Remove this when we get rid of the fake one, and make this
         // test loading one from disk somewhere.
         EXPECT_CALL(*(listener.get()),
-                    OnConfigUpdated(ConfigKeyEq(0, "fake"), StatsdConfigEq("12345")))
+                    OnConfigUpdated(ConfigKeyEq(1000, "fake"), StatsdConfigEq("12345")))
                 .RetiresOnSaturation();
         manager->Startup();
 
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index bb4930a..40c0e9d 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -27,7 +27,7 @@
 using std::unordered_map;
 using std::vector;
 
-const int TAG_ID = 123;
+const int32_t TAG_ID = 123;
 const int FIELD_ID_1 = 1;
 const int FIELD_ID_2 = 2;
 const int FIELD_ID_3 = 2;
@@ -43,8 +43,6 @@
     simpleMatcher->set_tag(TAG_ID);
 
     LogEvent event(TAG_ID, 0);
-
-    // Convert to a LogEvent
     event.init();
 
     // Test
@@ -63,10 +61,8 @@
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
-    auto list = event.GetAndroidLogEventList();
-    *list << true;
-    *list << false;
-
+    event.write(true);
+    event.write(false);
     // Convert to a LogEvent
     event.init();
 
@@ -99,9 +95,7 @@
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
-    auto list = event.GetAndroidLogEventList();
-    *list << "some value";
-
+    event.write("some value");
     // Convert to a LogEvent
     event.init();
 
@@ -121,9 +115,8 @@
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
-    auto list = event.GetAndroidLogEventList();
-    *list << 2;
-    *list << 3;
+    event.write(2);
+    event.write(3);
 
     // Convert to a LogEvent
     event.init();
@@ -153,9 +146,7 @@
 
     // Set up the event
     LogEvent event(TAG_ID, 0);
-    auto list = event.GetAndroidLogEventList();
-    *list << 11;
-
+    event.write(11);
     event.init();
 
     // Test
@@ -201,8 +192,6 @@
     EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
 }
 
-#if 0
-
 TEST(LogEntryMatcherTest, TestFloatComparisonMatcher) {
     // Set up the matcher
     LogEntryMatcher matcher;
@@ -212,22 +201,28 @@
     auto keyValue = simpleMatcher->add_key_value_matcher();
     keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
 
-    LogEvent event;
-    event.tagId = TAG_ID;
-
+    LogEvent event1(TAG_ID, 0);
     keyValue->set_lt_float(10.0);
-    event.floatMap[FIELD_ID_1] = 10.1;
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
-    event.floatMap[FIELD_ID_1] = 9.9;
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    event1.write(10.1f);
+    event1.init();
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event1));
 
+    LogEvent event2(TAG_ID, 0);
+    event2.write(9.9f);
+    event2.init();
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event2));
+
+    LogEvent event3(TAG_ID, 0);
+    event3.write(10.1f);
+    event3.init();
     keyValue->set_gt_float(10.0);
-    event.floatMap[FIELD_ID_1] = 10.1;
-    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
-    event.floatMap[FIELD_ID_1] = 9.9;
-    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event3));
+
+    LogEvent event4(TAG_ID, 0);
+    event4.write(9.9f);
+    event4.init();
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event4));
 }
-#endif
 
 // Helper for the composite matchers.
 void addSimpleMatcher(SimpleLogEntryMatcher* simpleMatcher, int tag, int key, int val) {
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index caa1cf4..3dd4e70 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -34,6 +34,7 @@
 using std::set;
 using std::unordered_map;
 using std::vector;
+using android::os::statsd::Condition;
 
 #ifdef __ANDROID__
 
@@ -71,6 +72,19 @@
     combination->add_matcher("SCREEN_IS_ON");
     combination->add_matcher("SCREEN_IS_OFF");
 
+    CountMetric* metric = config.add_count_metric();
+    metric->set_name("3");
+    metric->set_what("SCREEN_IS_ON");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(1);
+
+    auto alert = config.add_alert();
+    alert->set_name("3");
+    alert->set_metric_name("3");
+    alert->set_number_of_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
     return config;
 }
 
@@ -100,6 +114,29 @@
     return config;
 }
 
+StatsdConfig buildAlertWithUnknownMetric() {
+    StatsdConfig config;
+    config.set_name("12345");
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_ON");
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_name("3");
+    metric->set_what("SCREEN_IS_ON");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(1);
+
+    auto alert = config.add_alert();
+    alert->set_name("3");
+    alert->set_metric_name("2");
+    alert->set_number_of_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
 StatsdConfig buildMissingMatchers() {
     StatsdConfig config;
     config.set_name("12345");
@@ -156,6 +193,12 @@
     KeyMatcher* keyMatcher = metric->add_dimension();
     keyMatcher->set_key(1);
 
+    auto alert = config.add_alert();
+    alert->set_name("3");
+    alert->set_metric_name("3");
+    alert->set_number_of_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
     return config;
 }
 
@@ -183,7 +226,7 @@
     simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
             1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
 
-    Condition* condition = config.add_condition();
+    auto condition = config.add_condition();
     condition->set_name("SCREEN_IS_ON");
     SimpleCondition* simpleCondition = condition->mutable_simple_condition();
     simpleCondition->set_start("SCREEN_IS_ON");
@@ -206,13 +249,16 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_TRUE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                 allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                 trackerToConditionMap));
+                                 allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                 trackerToMetricMap, trackerToConditionMap));
+    EXPECT_EQ(1u, allMetricProducers.size());
+    EXPECT_EQ(1u, allAnomalyTrackers.size());
 }
 
 TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
@@ -221,13 +267,14 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
@@ -236,13 +283,14 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestMissingMatchers) {
@@ -251,13 +299,13 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 TEST(MetricsManagerTest, TestCircleConditionDependency) {
@@ -266,13 +314,30 @@
     vector<sp<LogMatchingTracker>> allLogEntryMatchers;
     vector<sp<ConditionTracker>> allConditionTrackers;
     vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
 
     EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
-                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                                  trackerToConditionMap));
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+    StatsdConfig config = buildAlertWithUnknownMetric();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
+                                  trackerToMetricMap, trackerToConditionMap));
 }
 
 #else
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index c64719e..0c19468 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -34,12 +34,12 @@
 
 TEST(UidMapTest, TestIsolatedUID) {
     sp<UidMap> m = new UidMap();
-    StatsLogProcessor p(m, nullptr);
+    sp<AnomalyMonitor> anomalyMonitor;
+    StatsLogProcessor p(m, anomalyMonitor, nullptr);
     LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
-    android_log_event_list* list = addEvent.GetAndroidLogEventList();
-    *list << 100;  // parent UID
-    *list << 101;  // isolated UID
-    *list << 1;    // Indicates creation.
+    addEvent.write(100);  // parent UID
+    addEvent.write(101);  // isolated UID
+    addEvent.write(1);    // Indicates creation.
     addEvent.init();
 
     EXPECT_EQ(101, m->getParentUidOrSelf(101));
@@ -48,10 +48,9 @@
     EXPECT_EQ(100, m->getParentUidOrSelf(101));
 
     LogEvent removeEvent(android::util::ISOLATED_UID_CHANGED, 1);
-    list = removeEvent.GetAndroidLogEventList();
-    *list << 100;  // parent UID
-    *list << 101;  // isolated UID
-    *list << 0;    // Indicates removal.
+    removeEvent.write(100);  // parent UID
+    removeEvent.write(101);  // isolated UID
+    removeEvent.write(0);    // Indicates removal.
     removeEvent.init();
     p.OnLogEvent(removeEvent);
     EXPECT_EQ(101, m->getParentUidOrSelf(101));
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index b8150d0..e0200f27 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/anomaly/DiscreteAnomalyTracker.h"
+#include "src/anomaly/AnomalyTracker.h"
 
 #include <gtest/gtest.h>
 #include <stdio.h>
@@ -37,7 +37,7 @@
     }
 }
 
-std::shared_ptr<DimToValMap> MockeBucket(
+std::shared_ptr<DimToValMap> MockBucket(
         const std::vector<std::pair<string, long>>& key_value_pair_list) {
     std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
     AddValueToBucket(key_value_pair_list, bucket);
@@ -45,190 +45,240 @@
 }
 
 TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
+    const int64_t bucketSizeNs = 30 * NS_PER_SEC;
     Alert alert;
     alert.set_number_of_buckets(3);
-    alert.set_refractory_period_in_buckets(3);
+    alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    DiscreteAnomalyTracker anomaly_tracker(alert);
+    AnomalyTracker anomalyTracker(alert, bucketSizeNs);
 
-    std::shared_ptr<DimToValMap> bucket0 = MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}});
-    // Adds bucket #0
-    anomaly_tracker.addOrUpdateBucket(bucket0, 0);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+    std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+    int64_t eventTimestamp0 = 10;
+    std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
+    int64_t eventTimestamp1 = bucketSizeNs + 11;
+    std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
+    int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
+    std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
+    int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
+    std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
+    int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
+    std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
+    int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
+    std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
+    int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
 
-    // Adds bucket #0 again. The sum does not change.
-    anomaly_tracker.addOrUpdateBucket(bucket0, 0);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 0L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
 
-    // Adds bucket #1.
-    std::shared_ptr<DimToValMap> bucket1 = MockeBucket({{"b", 2}});
-    anomaly_tracker.addOrUpdateBucket(bucket1, 1);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    // Alarm.
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    // Adds past bucket #0
+    anomalyTracker.addPastBucket(bucket0, 0);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
 
-    // Adds bucket #1 again. The sum does not change.
-    anomaly_tracker.addOrUpdateBucket(bucket1, 1);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    // Alarm.
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    // Adds past bucket #0 again. The sum does not change.
+    anomalyTracker.addPastBucket(bucket0, 0);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
 
-    // Adds bucket #2.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 2);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 2L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Adds past bucket #1.
+    anomalyTracker.addPastBucket(bucket1, 1);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Adds past bucket #1 again. Nothing changes.
+    anomalyTracker.addPastBucket(bucket1, 1);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+
+    // Adds past bucket #2.
+    anomalyTracker.addPastBucket(bucket2, 2);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
     // Within refractory period.
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
 
     // Adds bucket #3.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 3);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 3L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+    anomalyTracker.addPastBucket(bucket3, 3L);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
 
-    // Adds bucket #3.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 2}}), 4);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 4L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    // Within refractory period.
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+    // Adds bucket #4.
+    anomalyTracker.addPastBucket(bucket4, 4);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
 
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 5);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 5L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+    // Adds bucket #5.
+    anomalyTracker.addPastBucket(bucket5, 5);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
     // Within refractory period.
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 5L);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
 }
 
 TEST(AnomalyTrackerTest, TestSparseBuckets) {
+    const int64_t bucketSizeNs = 30 * NS_PER_SEC;
     Alert alert;
     alert.set_number_of_buckets(3);
-    alert.set_refractory_period_in_buckets(3);
+    alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
     alert.set_trigger_if_sum_gt(2);
 
-    DiscreteAnomalyTracker anomaly_tracker(alert);
+    AnomalyTracker anomalyTracker(alert, bucketSizeNs);
 
-    // Add bucket #9
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}}), 9);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 9L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+    std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
+    std::shared_ptr<DimToValMap> bucket18 = MockBucket({{"b", 1}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket20 = MockBucket({{"b", 3}, {"c", 1}});
+    std::shared_ptr<DimToValMap> bucket25 = MockBucket({{"d", 1}});
+    std::shared_ptr<DimToValMap> bucket28 = MockBucket({{"e", 2}});
 
-    // Add bucket #16
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 4}}), 16);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 16L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+    int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
+    int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
+    int64_t eventTimestamp3 = bucketSizeNs * 17 + 1;
+    int64_t eventTimestamp4 = bucketSizeNs * 19 + 2;
+    int64_t eventTimestamp5 = bucketSizeNs * 24 + 3;
+    int64_t eventTimestamp6 = bucketSizeNs * 27 + 3;
 
-    // Add bucket #18
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1);
+
+    // Add past bucket #9
+    anomalyTracker.addPastBucket(bucket9, 9);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
+
+    // Add past bucket #16
+    anomalyTracker.addPastBucket(bucket16, 16);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
     // Within refractory period.
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
 
-    // Add bucket #18 again.
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+    // Add past bucket #18
+    anomalyTracker.addPastBucket(bucket18, 18);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
-    // Add bucket #20
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 3}, {"d", 1}}), 20);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 20L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+    // Add bucket #18 again. Nothing changes.
+    anomalyTracker.addPastBucket(bucket18, 18);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
+    // Within refractory period.
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
-    // Add bucket #25
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"d", 1}}), 25);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 25L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1L);
-    EXPECT_FALSE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+    // Add past bucket #20
+    anomalyTracker.addPastBucket(bucket20, 20);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 3LL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
 
-    // Add bucket #28
-    anomaly_tracker.addOrUpdateBucket(MockeBucket({{"e", 5}}), 28);
-    EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 28L);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
-    EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("e")->second, 5L);
-    EXPECT_TRUE(anomaly_tracker.detectAnomaly());
-    anomaly_tracker.declareAndDeclareAnomaly();
-    EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 3L);
-    EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 28L);
+    // Add past bucket #25
+    anomalyTracker.addPastBucket(bucket25, 25);
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
+    EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
+    EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+
+    // Updates current bucket #28.
+    (*bucket28)["e"] = 5;
+    EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
+    EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
+    EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
+    EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7);
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 855e666..11fb011 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -46,10 +46,9 @@
 }
 
 void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) {
-    auto list = event->GetAndroidLogEventList();
-    *list << uid;  // uid
-    *list << wl;
-    *list << acquire;
+    event->write(uid);  // uid
+    event->write(wl);
+    event->write(acquire);
     event->init();
 }
 
@@ -71,6 +70,7 @@
     simpleCondition.set_start("SCREEN_TURNED_ON");
     simpleCondition.set_stop("SCREEN_TURNED_OFF");
     simpleCondition.set_count_nesting(false);
+    simpleCondition.set_initial_value(SimpleCondition_InitialValue_UNKNOWN);
 
     unordered_map<string, int> trackerNameIndexMap;
     trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp
index 600b953..d6cd876 100644
--- a/cmds/statsd/tests/indexed_priority_queue_test.cpp
+++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp
@@ -22,10 +22,12 @@
 
 /** struct for template in indexed_priority_queue */
 struct AATest : public RefBase {
-    AATest(uint32_t val) : val(val) {
+    AATest(uint32_t val, std::string a, std::string b) : val(val), a(a), b(b) {
     }
 
     const int val;
+    const std::string a;
+    const std::string b;
 
     struct Smaller {
         bool operator()(const sp<const AATest> a, const sp<const AATest> b) const {
@@ -36,9 +38,11 @@
 
 #ifdef __ANDROID__
 TEST(indexed_priority_queue, empty_and_size) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4 = new AATest{4};
-    sp<const AATest> aa8 = new AATest{8};
+    sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
 
     EXPECT_EQ(0u, ipq.size());
     EXPECT_TRUE(ipq.empty());
@@ -61,13 +65,15 @@
 }
 
 TEST(indexed_priority_queue, top) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa2 = new AATest{2};
-    sp<const AATest> aa4 = new AATest{4};
-    sp<const AATest> aa8 = new AATest{8};
-    sp<const AATest> aa12 = new AATest{12};
-    sp<const AATest> aa16 = new AATest{16};
-    sp<const AATest> aa20 = new AATest{20};
+    sp<const AATest> aa2 = new AATest{2, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa12 = new AATest{12, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa16 = new AATest{16, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa20 = new AATest{20, emptyMetricId, emptyDimensionId};
 
     EXPECT_EQ(ipq.top(), nullptr);
 
@@ -113,9 +119,11 @@
 }
 
 TEST(indexed_priority_queue, push_same_aa) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4_a = new AATest{4};
-    sp<const AATest> aa4_b = new AATest{4};
+    sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4_a);
     EXPECT_EQ(1u, ipq.size());
@@ -134,9 +142,11 @@
 }
 
 TEST(indexed_priority_queue, remove_nonexistant) {
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4 = new AATest{4};
-    sp<const AATest> aa5 = new AATest{5};
+    sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa5 = new AATest{5, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4);
     ipq.remove(aa5);
@@ -147,8 +157,10 @@
 
 TEST(indexed_priority_queue, remove_same_aa) {
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> aa4_a = new AATest{4};
-    sp<const AATest> aa4_b = new AATest{4};
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
+    sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
+    sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
 
     ipq.push(aa4_a);
     ipq.push(aa4_b);
@@ -184,9 +196,11 @@
 
 TEST(indexed_priority_queue, pop) {
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
-    sp<const AATest> a = new AATest{1};
-    sp<const AATest> b = new AATest{2};
-    sp<const AATest> c = new AATest{3};
+    std::string emptyMetricId;
+    std::string emptyDimensionId;
+    sp<const AATest> a = new AATest{1, emptyMetricId, emptyDimensionId};
+    sp<const AATest> b = new AATest{2, emptyMetricId, emptyDimensionId};
+    sp<const AATest> c = new AATest{3, emptyMetricId, emptyDimensionId};
 
     ipq.push(c);
     ipq.push(b);
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index d07a84d..35e08af 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -54,21 +54,26 @@
     // 2 events in bucket 1.
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+    // Flushes at event #2.
+    countProducer.flushIfNeeded(bucketStartTimeNs + 2);
+    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+    // Flushes.
+    countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
     const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
     EXPECT_EQ(1UL, buckets.size());
-    const auto& bucketInfo = buckets[0];
-    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
-    EXPECT_EQ(2LL, bucketInfo.mCount);
+    EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
+    EXPECT_EQ(2LL, buckets[0].mCount);
 
     // 1 matched event happens in bucket 2.
     LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    countProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
@@ -79,7 +84,7 @@
     EXPECT_EQ(1LL, bucketInfo2.mCount);
 
     // nothing happens in bucket 3. we should not record anything for bucket 3.
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+    countProducer.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
@@ -108,20 +113,22 @@
     EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
 
     countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
+    // Upon this match event, the matched event1 is flushed.
     countProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
     EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
 
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-
+    countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
-    const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
-    EXPECT_EQ(1UL, buckets.size());
-    const auto& bucketInfo = buckets[0];
-    EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
-    EXPECT_EQ(1LL, bucketInfo.mCount);
+    {
+        const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+        EXPECT_EQ(1UL, buckets.size());
+        const auto& bucketInfo = buckets[0];
+        EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+        EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+        EXPECT_EQ(1LL, bucketInfo.mCount);
+    }
 }
 
 TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
@@ -138,15 +145,13 @@
     link->add_key_in_condition()->set_key(2);
 
     LogEvent event1(1, bucketStartTimeNs + 1);
-    auto list = event1.GetAndroidLogEventList();
-    *list << "111";  // uid
+    event1.write("111");  // uid
     event1.init();
     ConditionKey key1;
     key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
 
     LogEvent event2(1, bucketStartTimeNs + 10);
-    auto list2 = event2.GetAndroidLogEventList();
-    *list2 << "222";  // uid
+    event2.write("222");  // uid
     event2.init();
     ConditionKey key2;
     key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
@@ -160,10 +165,11 @@
                                       bucketStartTimeNs);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+    countProducer.flushIfNeeded(bucketStartTimeNs + 1);
+    EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
-
-    countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-
+    countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
     EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
                 countProducer.mPastBuckets.end());
@@ -175,6 +181,68 @@
     EXPECT_EQ(1LL, bucketInfo.mCount);
 }
 
+TEST(CountMetricProducerTest, TestAnomalyDetection) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(2);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 30 * NS_PER_SEC;
+    int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+    int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+
+    CountMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+                                      bucketStartTimeNs);
+    countProducer.addAnomalyTracker(anomalyTracker);
+
+    int tagId = 1;
+    LogEvent event1(tagId, bucketStartTimeNs + 1);
+    LogEvent event2(tagId, bucketStartTimeNs + 2);
+    LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1);
+    LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
+    LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
+    LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
+    LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3 + NS_PER_SEC);
+
+    // Two events in bucket #0.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+    // One event in bucket #2. No alarm as bucket #0 is trashed out.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+    // Two events in bucket #3.
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5, false);
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6, false);
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
+    // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs());
+
+    countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7, false);
+    EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
+    EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index 0971d26..18d177c 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -95,15 +95,13 @@
     link->add_key_in_condition()->set_key(2);
 
     LogEvent event1(1, bucketStartTimeNs + 1);
-    auto list = event1.GetAndroidLogEventList();
-    *list << "111";  // uid
+    event1.write("111");  // uid
     event1.init();
     ConditionKey key1;
     key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
 
     LogEvent event2(1, bucketStartTimeNs + 10);
-    auto list2 = event2.GetAndroidLogEventList();
-    *list2 << "222";  // uid
+    event2.write("222");  // uid
     event2.init();
     ConditionKey key2;
     key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
new file mode 100644
index 0000000..b9e2b8a
--- /dev/null
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -0,0 +1,201 @@
+// 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 "logd/LogEvent.h"
+#include "metrics_test_helper.h"
+#include "src/metrics/GaugeMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(GaugeMetricProducerTest, TestWithCondition) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+    GaugeMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_gauge_field(2);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    GaugeMetricProducer gaugeProducer(metric, 1 /*has condition*/, wizard, -1, bucketStartTimeNs);
+
+    vector<std::shared_ptr<LogEvent>> allData;
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+    event1->write(1);
+    event1->write(13);
+    event1->init();
+    allData.push_back(event1);
+
+    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
+    event2->write(1);
+    event2->write(15);
+    event2->init();
+    allData.push_back(event2);
+
+    gaugeProducer.onDataPulled(allData);
+    gaugeProducer.flushIfNeeded(event2->GetTimestampNs() + 1);
+    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
+
+    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 11);
+    gaugeProducer.onConditionChanged(false, bucketStartTimeNs + 21);
+    gaugeProducer.onConditionChanged(true, bucketStartTimeNs + bucketSizeNs + 11);
+    std::shared_ptr<LogEvent> event3 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write(1);
+    event3->write(25);
+    event3->init();
+    allData.push_back(event3);
+    gaugeProducer.onDataPulled(allData);
+    gaugeProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    // One dimension.
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
+    EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              gaugeProducer.mPastBuckets.begin()->second.front().mBucketStartNs);
+}
+
+TEST(GaugeMetricProducerTest, TestNoCondition) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+    GaugeMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_gauge_field(2);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+
+    vector<std::shared_ptr<LogEvent>> allData;
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+    event1->write(1);
+    event1->write(13);
+    event1->init();
+    allData.push_back(event1);
+
+    std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
+    event2->write(1);
+    event2->write(15);
+    event2->init();
+    allData.push_back(event2);
+
+    std::shared_ptr<LogEvent> event3 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write(1);
+    event3->write(25);
+    event3->init();
+    allData.push_back(event3);
+
+    gaugeProducer.onDataPulled(allData);
+    // Has one slice
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
+    EXPECT_EQ(13L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
+    EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
+    EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mGauge);
+    EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              gaugeProducer.mPastBuckets.begin()->second.back().mBucketStartNs);
+}
+
+TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    GaugeMetric metric;
+    metric.set_name("1");
+    metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+    metric.set_gauge_field(2);
+    GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
+
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(25);
+    alert.set_number_of_buckets(2);
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    gaugeProducer.addAnomalyTracker(anomalyTracker);
+
+    std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
+    event1->write(1);
+    event1->write(13);
+    event1->init();
+
+    gaugeProducer.onDataPulled({event1});
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+
+    std::shared_ptr<LogEvent> event2 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10);
+    event2->write(1);
+    event2->write(15);
+    event2->init();
+
+    gaugeProducer.onDataPulled({event2});
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
+
+    std::shared_ptr<LogEvent> event3 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+    event3->write(1);
+    event3->write(24);
+    event3->init();
+
+    gaugeProducer.onDataPulled({event3});
+    EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+
+    // The event4 does not have the gauge field. Thus the current bucket value is 0.
+    std::shared_ptr<LogEvent> event4 =
+            std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10);
+    event4->write(1);
+    event4->init();
+    gaugeProducer.onDataPulled({event4});
+    EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
+    EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 58bf1b3..9e169bb 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -45,17 +45,49 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
 
-    tracker.noteStart("", true, bucketStartTimeNs, key1);
-    tracker.noteStop("", bucketStartTimeNs + 10);
+    tracker.noteStart("1", true, bucketStartTimeNs, key1);
+    // Event starts again. This would not change anything as it already starts.
+    tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
+    // Stopped.
+    tracker.noteStop("1", bucketStartTimeNs + 10, false);
 
-    tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
-    tracker.noteStop("", bucketStartTimeNs + 40);
+    // Another event starts in this bucket.
+    tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(20, buckets[0].mDuration);
+    EXPECT_EQ(20ULL, buckets[0].mDuration);
+}
+
+TEST(MaxDurationTrackerTest, TestStopAll) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucket> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
+
+    tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
+
+    // Another event starts in this bucket.
+    tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
+    tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
+    EXPECT_TRUE(tracker.mInfos.empty());
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(40ULL, buckets[1].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
@@ -67,14 +99,67 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
 
+    // The event starts.
     tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+
+    // Starts again. Does not change anything.
+    tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
+
+    // Flushes at early 2nd bucket. The event still does not stop yet.
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+
+    // Flushes at the end of the 2nd bucket. The event still does not stop yet.
+    tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs));
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+    EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
+
+    // The event stops at early 4th bucket.
+    tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
+    tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 21);
+    EXPECT_EQ(3u, buckets.size());
+    EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
+    EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[2].mDuration);
+}
+
+TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucket> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
+
+    // 2 starts
+    tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+    tracker.noteStart("", true, bucketStartTimeNs + 10, key1);
+    // one stop
+    tracker.noteStop("", bucketStartTimeNs + 20, false /*stop all*/);
+
     tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
 
     EXPECT_EQ(2u, buckets.size());
-    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
-    EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+
+    // real stop now.
+    tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
+    tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1);
+
+    EXPECT_EQ(3u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ(5ULL, buckets[2].mDuration);
 }
 
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
@@ -93,17 +178,65 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    MaxDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+                               buckets);
+    EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
 
-    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+    EXPECT_TRUE(tracker.mInfos.empty());
+    EXPECT_EQ(6LL, tracker.mDuration);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+    tracker.noteStart("2:maps", false, eventStartTimeNs + 3 * bucketSizeNs + 10, key1);
+    EXPECT_EQ(1u, tracker.mInfos.size());
+    for (const auto& itr : tracker.mInfos) {
+        EXPECT_EQ(DurationState::kPaused, itr.second.state);
+        EXPECT_EQ(0LL, itr.second.lastDuration);
+    }
+    EXPECT_EQ(3u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+    EXPECT_EQ(6ULL, buckets[2].mDuration);
+}
 
-    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
-    EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(5, buckets[0].mDuration);
+TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    vector<DurationBucket> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs,
+                               {anomalyTracker}, buckets);
+
+    tracker.noteStart("1", true, eventStartTimeNs, key1);
+    tracker.noteStop("1", eventStartTimeNs + 10, false);
+    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+    EXPECT_EQ(10LL, tracker.mDuration);
+
+    tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
+    tracker.noteStop("2", eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
+    EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
+    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
+              (long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 74a6f11..f4edffd 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -45,18 +45,105 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
-    int64_t durationTimeNs = 2 * 1000;
+    uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+    EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
+    tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestDurationNested) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    OringDurationTracker tracker("event", wizard, 1, true, 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.noteStop("2:maps", eventStartTimeNs + 2000, false);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
+    EXPECT_EQ(2003ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestStopAll) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+
+    tracker.noteStopAll(eventStartTimeNs + 2003);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(2003ULL, buckets[0].mDuration);
+}
+
+TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+    uint64_t durationTimeNs = 2 * 1000;
+
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
+    EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
+
+    tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
+    tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
 }
 
 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
@@ -73,20 +160,151 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
-    int64_t durationTimeNs = 2 * 1000;
+    uint64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
 
-    tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
+    tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + durationTimeNs);
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
+    EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
+}
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    EXPECT_CALL(*wizard, query(_, key1))  // #4
+            .WillOnce(Return(ConditionState::kFalse));
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
+                                 buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 3, false);
+
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 15);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
-    EXPECT_EQ(5, buckets[0].mDuration);
+    EXPECT_EQ(15ULL, buckets[0].mDuration);
 }
+
+TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    vector<DurationBucket> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs,
+                                 {anomalyTracker}, buckets);
+
+    // Nothing in the past bucket.
+    tracker.noteStart("", true, eventStartTimeNs, key1);
+    EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
+
+    tracker.noteStop("", eventStartTimeNs + 3, false);
+    EXPECT_EQ(0u, buckets.size());
+
+    uint64_t event1StartTimeNs = eventStartTimeNs + 10;
+    tracker.noteStart("1", true, event1StartTimeNs, key1);
+    // No past buckets. The anomaly will happen in bucket #0.
+    EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
+
+    uint64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
+    tracker.noteStop("1", event1StopTimeNs, false);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
+              buckets[0].mDuration);
+
+    const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10;
+    const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs;
+
+    // One past buckets. The anomaly will happen in bucket #1.
+    uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
+    tracker.noteStart("1", true, event2StartTimeNs, key1);
+    EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
+                          bucket1Duration),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
+    tracker.noteStop("1", event2StartTimeNs + 1, false);
+
+    // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
+    // bucket #2.
+    uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
+    tracker.noteStart("1", true, event3StartTimeNs, key1);
+    EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
+              tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
+}
+
+TEST(OringDurationTrackerTest, TestAnomalyDetection) {
+    Alert alert;
+    alert.set_name("alert");
+    alert.set_metric_name("1");
+    alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
+    alert.set_number_of_buckets(2);
+    alert.set_refractory_period_secs(1);
+
+    vector<DurationBucket> buckets;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+    uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
+    uint64_t bucketSizeNs = 30 * NS_PER_SEC;
+
+    sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
+    OringDurationTracker tracker("event", wizard, 1, true /*nesting*/, bucketStartTimeNs,
+                                 bucketSizeNs, {anomalyTracker}, buckets);
+
+    tracker.noteStart("", true, eventStartTimeNs, key1);
+    tracker.noteStop("", eventStartTimeNs + 10, false);
+    EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+    EXPECT_TRUE(tracker.mStarted.empty());
+    EXPECT_EQ(-1LL, tracker.mLastStartTime);
+    EXPECT_EQ(10LL, tracker.mDuration);
+
+    EXPECT_EQ(0u, tracker.mStarted.size());
+
+    tracker.noteStart("", true, eventStartTimeNs + 20, key1);
+    EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
+    EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
+              (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
+    tracker.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
+    EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
+    EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
+              anomalyTracker->mLastAlarmTimestampNs);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 72b4194..1ed3636 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -64,9 +64,8 @@
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
     shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
-    auto list = event->GetAndroidLogEventList();
-    *list << 1;
-    *list << 11;
+    event->write(1);
+    event->write(11);
     event->init();
     allData.push_back(event);
 
@@ -89,9 +88,8 @@
 
     allData.clear();
     event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    list = event->GetAndroidLogEventList();
-    *list << 1;
-    *list << 22;
+    event->write(1);
+    event->write(22);
     event->init();
     allData.push_back(event);
     valueProducer.onDataPulled(allData);
@@ -110,9 +108,8 @@
 
     allData.clear();
     event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
-    list = event->GetAndroidLogEventList();
-    *list << 1;
-    *list << 33;
+    event->write(1);
+    event->write(33);
     event->init();
     allData.push_back(event);
     valueProducer.onDataPulled(allData);
@@ -159,9 +156,8 @@
         int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
         data->clear();
         shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-        auto list = event->GetAndroidLogEventList();
-        *list << 1;
-        *list << 100;
+        event->write(1);
+        event->write(100);
         event->init();
         data->push_back(event);
         return true;
@@ -174,9 +170,8 @@
         int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
         data->clear();
         shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
-        auto list = event->GetAndroidLogEventList();
-        *list << 1;
-        *list << 120;
+        event->write(1);
+        event->write(120);
         event->init();
         data->push_back(event);
         return true;
@@ -201,9 +196,8 @@
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
     shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
-    auto list = event->GetAndroidLogEventList();
-    *list << 1;
-    *list << 110;
+    event->write(1);
+    event->write(110);
     event->init();
     allData.push_back(event);
     valueProducer.onDataPulled(allData);
@@ -253,14 +247,12 @@
                                       bucketStartTimeNs, pullerManager);
 
     shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    auto list = event1->GetAndroidLogEventList();
-    *list << 1;
-    *list << 10;
+    event1->write(1);
+    event1->write(10);
     event1->init();
     shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
-    auto list2 = event2->GetAndroidLogEventList();
-    *list2 << 1;
-    *list2 << 20;
+    event2->write(1);
+    event2->write(20);
     event2->init();
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1, false);
     // has one slice
@@ -285,7 +277,7 @@
     EXPECT_EQ(20, curInterval.raw.back().first);
     EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
 
-    valueProducer.flush_if_needed(bucket3StartTimeNs);
+    valueProducer.flushIfNeeded(bucket3StartTimeNs);
     EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
     EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
     EXPECT_EQ(30, valueProducer.mPastBuckets.begin()->second.back().mValue);
diff --git a/cmds/webview_zygote/Android.mk b/cmds/webview_zygote/Android.mk
index 66e762c..955e58e 100644
--- a/cmds/webview_zygote/Android.mk
+++ b/cmds/webview_zygote/Android.mk
@@ -21,6 +21,8 @@
 
 LOCAL_SRC_FILES := webview_zygote.cpp
 
+LOCAL_CFLAGS := -Wall -Werror
+
 LOCAL_SHARED_LIBRARIES := \
 	libandroid_runtime \
 	libbinder \
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a558d68..8824643 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -358,6 +358,11 @@
      */
     public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
 
+    /**
+     * Action to lock the screen
+     */
+    public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
     private static final String LOG_TAG = "AccessibilityService";
 
     /**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 99f3dee..03a3631 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4379,7 +4379,7 @@
             throw new IllegalArgumentException("requestCode should be >= 0");
         }
         if (mHasCurrentPermissionsRequest) {
-            Log.w(TAG, "Can reqeust only one set of permissions at a time");
+            Log.w(TAG, "Can request only one set of permissions at a time");
             // Dispatch the callback with empty arrays which means a cancellation.
             onRequestPermissionsResult(requestCode, new String[0], new int[0]);
             return;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 21e454f..95c5fb5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5380,7 +5380,7 @@
             }
         }
 
-        GraphicsEnvironment.chooseDriver(context);
+        GraphicsEnvironment.getInstance().setup(context);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index b7c1f4e..7257044 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -17,9 +17,12 @@
 package android.app;
 
 import android.os.Build;
+import android.os.GraphicsEnvironment;
 import android.os.Trace;
 import android.util.ArrayMap;
+
 import com.android.internal.os.ClassLoaderFactory;
+
 import dalvik.system.PathClassLoader;
 
 /** @hide */
@@ -72,8 +75,9 @@
 
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
-                setupVulkanLayerPath(classloader, librarySearchPath);
+                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setLayerPaths");
+                GraphicsEnvironment.getInstance().setLayerPaths(
+                        classloader, librarySearchPath, libraryPermittedPath);
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
                 mLoaders.put(cacheKey, classloader);
@@ -105,8 +109,6 @@
                               cacheKey, null /* classLoaderName */);
     }
 
-    private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
-
     /**
      * Adds a new path the classpath of the given loader.
      * @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0eafdec..7a4c00f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -35,7 +35,6 @@
 import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
@@ -1681,21 +1680,8 @@
     }
 
     @Override
-    public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
-                               String installerPackageName) {
-        installCommon(packageURI, new LegacyPackageInstallObserver(observer), flags,
-                installerPackageName, mContext.getUserId());
-    }
-
-    @Override
-    public void installPackage(Uri packageURI, PackageInstallObserver observer,
-            int flags, String installerPackageName) {
-        installCommon(packageURI, observer, flags, installerPackageName, mContext.getUserId());
-    }
-
-    private void installCommon(Uri packageURI,
-            PackageInstallObserver observer, int flags, String installerPackageName,
-            int userId) {
+    public void installPackage(Uri packageURI,
+            PackageInstallObserver observer, int flags, String installerPackageName) {
         if (!"file".equals(packageURI.getScheme())) {
             throw new UnsupportedOperationException("Only file:// URIs are supported");
         }
@@ -1703,7 +1689,7 @@
         final String originPath = packageURI.getPath();
         try {
             mPM.installPackageAsUser(originPath, observer.getBinder(), flags, installerPackageName,
-                    userId);
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d5d95fb..42c1347 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -68,6 +68,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 import android.view.Gravity;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -2447,6 +2448,30 @@
         notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
     }
 
+    /**
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(NotificationProto.CHANNEL_ID, getChannelId());
+        proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
+        proto.write(NotificationProto.FLAGS, this.flags);
+        proto.write(NotificationProto.COLOR, this.color);
+        proto.write(NotificationProto.CATEGORY, this.category);
+        proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
+        proto.write(NotificationProto.SORT_KEY, this.mSortKey);
+        if (this.actions != null) {
+            proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
+        }
+        if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
+            proto.write(NotificationProto.VISIBILITY, this.visibility);
+        }
+        if (publicVersion != null) {
+            publicVersion.writeToProto(proto, NotificationProto.PUBLIC_VERSION);
+        }
+        proto.end(token);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index e491a4f..da5569d 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -359,6 +359,8 @@
             if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition()
                     + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
                     + ", views=" + mNumReadViews);
+            mCurParcel.recycle();
+            mCurParcel = null; // Parcel cannot be used after recycled.
         }
 
         Parcel readParcel(int validateToken, int level) {
@@ -396,20 +398,23 @@
 
         private void fetchData() {
             Parcel data = Parcel.obtain();
-            data.writeInterfaceToken(DESCRIPTOR);
-            data.writeStrongBinder(mTransferToken);
-            if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
-            if (mCurParcel != null) {
-                mCurParcel.recycle();
-            }
-            mCurParcel = Parcel.obtain();
             try {
-                mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failure reading AssistStructure data", e);
-                throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+                data.writeInterfaceToken(DESCRIPTOR);
+                data.writeStrongBinder(mTransferToken);
+                if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
+                if (mCurParcel != null) {
+                    mCurParcel.recycle();
+                }
+                mCurParcel = Parcel.obtain();
+                try {
+                    mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failure reading AssistStructure data", e);
+                    throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+                }
+            } finally {
+                data.recycle();
             }
-            data.recycle();
             mNumReadWindows = mNumReadViews = 0;
         }
     }
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 616a5be..c5f2272 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -33,7 +33,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.widget.RemoteViews;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
@@ -41,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A slice is a piece of app content and actions that can be surfaced outside of the app.
@@ -54,7 +54,7 @@
      * @hide
      */
     @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
-            HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
+            HINT_NO_TINT, HINT_PARTIAL})
     public @interface SliceHint{ }
 
     /**
@@ -62,8 +62,7 @@
      * the content should be used in the shortcut representation of the slice (icon, label, action),
      * normally this should be indicated by adding the hint on the action containing that content.
      *
-     * @see SliceView#MODE_SHORTCUT
-     * @see SliceItem#TYPE_ACTION
+     * @see SliceItem#FORMAT_ACTION
      */
     public static final String HINT_TITLE       = "title";
     /**
@@ -91,27 +90,13 @@
      */
     public static final String HINT_SELECTED    = "selected";
     /**
-     * Hint to indicate that this is a message as part of a communication
-     * sequence in this slice.
-     */
-    public static final String HINT_MESSAGE     = "message";
-    /**
-     * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
-     */
-    public static final String HINT_SOURCE      = "source";
-    /**
-     * Hint that list items within this slice or subslice would appear better
-     * if organized horizontally.
-     */
-    public static final String HINT_HORIZONTAL  = "horizontal";
-    /**
      * Hint to indicate that this content should not be tinted.
      */
     public static final String HINT_NO_TINT     = "no_tint";
     /**
-     * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL}
-     * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate
-     * the {@link SliceView#MODE_SHORTCUT} format of the slice.
+     * Hint to indicate that this content should not be shown in larger renderings
+     * of Slices. This content may be used to populate the shortcut/icon
+     * format of the slice.
      * @hide
      */
     public static final String HINT_HIDDEN = "hidden";
@@ -124,32 +109,42 @@
      */
     public static final String HINT_TOGGLE = "toggle";
     /**
+     * Hint that list items within this slice or subslice would appear better
+     * if organized horizontally.
+     */
+    public static final String HINT_HORIZONTAL = "horizontal";
+    /**
      * Hint to indicate that this slice is incomplete and an update will be sent once
      * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
      * OS and should not be cached by apps.
      */
     public static final String HINT_PARTIAL     = "partial";
 
-    // These two are coming over from prototyping, but we probably don't want in
-    // public API, at least not right now.
-    /**
-     * @hide
-     */
-    public static final String HINT_ALT         = "alt";
     /**
      * Key to retrieve an extra added to an intent when a control is changed.
      * @hide
      */
     public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+    /**
+     * Subtype to indicate that this is a message as part of a communication
+     * sequence in this slice.
+     */
+    public static final String SUBTYPE_MESSAGE = "message";
+    /**
+     * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}.
+     */
+    public static final String SUBTYPE_SOURCE = "source";
 
     private final SliceItem[] mItems;
     private final @SliceHint String[] mHints;
+    private SliceSpec mSpec;
     private Uri mUri;
 
-    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) {
         mHints = hints;
         mItems = items.toArray(new SliceItem[items.size()]);
         mUri = uri;
+        mSpec = spec;
     }
 
     protected Slice(Parcel in) {
@@ -160,6 +155,14 @@
             mItems[i] = SliceItem.CREATOR.createFromParcel(in);
         }
         mUri = Uri.CREATOR.createFromParcel(in);
+        mSpec = in.readTypedObject(SliceSpec.CREATOR);
+    }
+
+    /**
+     * @return The spec for this slice
+     */
+    public @Nullable SliceSpec getSpec() {
+        return mSpec;
     }
 
     /**
@@ -191,6 +194,7 @@
             mItems[i].writeToParcel(dest, flags);
         }
         mUri.writeToParcel(dest, 0);
+        dest.writeTypedObject(mSpec, flags);
     }
 
     @Override
@@ -213,6 +217,7 @@
         private final Uri mUri;
         private ArrayList<SliceItem> mItems = new ArrayList<>();
         private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+        private SliceSpec mSpec;
 
         /**
          * Create a builder which will construct a {@link Slice} for the Given Uri.
@@ -248,11 +253,28 @@
         }
 
         /**
+         * Add the spec for this slice.
+         */
+        public Builder setSpec(SliceSpec spec) {
+            mSpec = spec;
+            return this;
+        }
+
+        /**
          * Add a sub-slice to the slice being constructed
          */
         public Builder addSubSlice(@NonNull Slice slice) {
-            mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
-                    new String[slice.getHints().size()])));
+            return addSubSlice(slice, null);
+        }
+
+        /**
+         * Add a sub-slice to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) {
+            mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType,
+                    slice.getHints().toArray(new String[slice.getHints().size()])));
             return this;
         }
 
@@ -260,99 +282,132 @@
          * Add an action to the slice being constructed
          */
         public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
-            mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
+            return addAction(action, s, null);
+        }
+
+        /**
+         * Add an action to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s,
+                @Nullable String subType) {
+            List<String> hints = s.getHints();
+            s.mSpec = null;
+            mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray(
+                    new String[hints.size()])));
             return this;
         }
 
         /**
          * Add text to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Builder addText(CharSequence text, @SliceHint String... hints) {
-            mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
+        public Builder addText(CharSequence text, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints));
             return this;
         }
 
         /**
          * Add text to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Builder addText(CharSequence text, @SliceHint List<String> hints) {
-            return addText(text, hints.toArray(new String[hints.size()]));
-        }
-
-        /**
-         * Add an image to the slice being constructed
-         */
-        public Builder addIcon(Icon icon, @SliceHint String... hints) {
-            mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
-            return this;
-        }
-
-        /**
-         * Add an image to the slice being constructed
-         */
-        public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
-            return addIcon(icon, hints.toArray(new String[hints.size()]));
-        }
-
-        /**
-         * @hide This isn't final
-         */
-        public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
-            mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
-            return this;
-        }
-
-        /**
-         * Add remote input to the slice being constructed
-         */
-        public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+        public Builder addText(CharSequence text, @Nullable String subType,
                 @SliceHint List<String> hints) {
-            return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+            return addText(text, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) {
+            mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints));
+            return this;
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) {
+            return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
         }
 
         /**
          * Add remote input to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
-            mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add remote input to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT,
+                    subType, hints));
             return this;
         }
 
         /**
          * Add a color to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Builder addColor(int color, @SliceHint String... hints) {
-            mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
+        public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) {
+            mItems.add(new SliceItem(color, SliceItem.FORMAT_COLOR, subType, hints));
             return this;
         }
 
         /**
          * Add a color to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Builder addColor(int color, @SliceHint List<String> hints) {
-            return addColor(color, hints.toArray(new String[hints.size()]));
+        public Builder addColor(int color, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addColor(color, subType, hints.toArray(new String[hints.size()]));
         }
 
         /**
          * Add a timestamp to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
-            mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
+        public Slice.Builder addTimestamp(long time, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType,
+                    hints));
             return this;
         }
 
         /**
          * Add a timestamp to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
          */
-        public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
-            return addTimestamp(time, hints.toArray(new String[hints.size()]));
+        public Slice.Builder addTimestamp(long time, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
         }
 
         /**
          * Construct the slice.
          */
         public Slice build() {
-            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
         }
     }
 
@@ -380,15 +435,15 @@
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < mItems.length; i++) {
             sb.append(indent);
-            if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
+            if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) {
                 sb.append("slice:\n");
                 sb.append(mItems[i].getSlice().toString(indent + "   "));
-            } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
+            } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) {
                 sb.append("text: ");
                 sb.append(mItems[i].getText());
                 sb.append("\n");
             } else {
-                sb.append(SliceItem.typeToString(mItems[i].getType()));
+                sb.append(mItems[i].getFormat());
                 sb.append("\n");
             }
         }
@@ -400,10 +455,12 @@
      *
      * @param resolver ContentResolver to be used.
      * @param uri The URI to a slice provider
+     * @param supportedSpecs List of supported specs.
      * @return The Slice provided by the app or null if none is given.
      * @see Slice
      */
-    public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
+    public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri,
+            List<SliceSpec> supportedSpecs) {
         Preconditions.checkNotNull(uri, "uri");
         IContentProvider provider = resolver.acquireProvider(uri);
         if (provider == null) {
@@ -412,6 +469,8 @@
         try {
             Bundle extras = new Bundle();
             extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+                    new ArrayList<>(supportedSpecs));
             final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
                     null, extras);
             Bundle.setDefusable(res, true);
@@ -435,12 +494,14 @@
      *
      * @param context The context to use.
      * @param intent The intent associated with a slice.
+     * @param supportedSpecs List of supported specs.
      * @return The Slice provided by the app or null if none is given.
      * @see Slice
      * @see SliceProvider#onMapIntentToUri(Intent)
      * @see Intent
      */
-    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
+            List<SliceSpec> supportedSpecs) {
         Preconditions.checkNotNull(intent, "intent");
         Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
                 "Slice intent must be explicit " + intent);
@@ -449,7 +510,7 @@
         // Check if the intent has data for the slice uri on it and use that
         final Uri intentData = intent.getData();
         if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
-            return bindSlice(resolver, intentData);
+            return bindSlice(resolver, intentData, supportedSpecs);
         }
         // Otherwise ask the app
         List<ResolveInfo> providers =
@@ -467,6 +528,8 @@
         try {
             Bundle extras = new Bundle();
             extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+                    new ArrayList<>(supportedSpecs));
             final Bundle res = provider.call(resolver.getPackageName(),
                     SliceProvider.METHOD_MAP_INTENT, null, extras);
             if (res == null) {
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index 6e69b05..8d81199 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -16,8 +16,8 @@
 
 package android.app.slice;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.StringDef;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
 import android.graphics.drawable.Icon;
@@ -38,13 +38,13 @@
  *
  * A SliceItem a piece of content and some hints about what that content
  * means or how it should be displayed. The types of content can be:
- * <li>{@link #TYPE_SLICE}</li>
- * <li>{@link #TYPE_TEXT}</li>
- * <li>{@link #TYPE_IMAGE}</li>
- * <li>{@link #TYPE_ACTION}</li>
- * <li>{@link #TYPE_COLOR}</li>
- * <li>{@link #TYPE_TIMESTAMP}</li>
- * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
  *
  * The hints that a {@link SliceItem} are a set of strings which annotate
  * the content. The hints that are guaranteed to be understood by the system
@@ -55,68 +55,68 @@
     /**
      * @hide
      */
-    @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
-            TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+    @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR,
+            FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT})
     public @interface SliceType {}
 
     /**
      * A {@link SliceItem} that contains a {@link Slice}
      */
-    public static final int TYPE_SLICE        = 1;
+    public static final String FORMAT_SLICE = "slice";
     /**
      * A {@link SliceItem} that contains a {@link CharSequence}
      */
-    public static final int TYPE_TEXT         = 2;
+    public static final String FORMAT_TEXT = "text";
     /**
      * A {@link SliceItem} that contains an {@link Icon}
      */
-    public static final int TYPE_IMAGE        = 3;
+    public static final String FORMAT_IMAGE = "image";
     /**
      * A {@link SliceItem} that contains a {@link PendingIntent}
      *
      * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
      * item contains a {@link Slice} that the action applies to.
      */
-    public static final int TYPE_ACTION       = 4;
-    /**
-     * @hide This isn't final
-     */
-    public static final int TYPE_REMOTE_VIEW  = 5;
+    public static final String FORMAT_ACTION = "action";
     /**
      * A {@link SliceItem} that contains a Color int.
      */
-    public static final int TYPE_COLOR        = 6;
+    public static final String FORMAT_COLOR = "color";
     /**
      * A {@link SliceItem} that contains a timestamp.
      */
-    public static final int TYPE_TIMESTAMP    = 8;
+    public static final String FORMAT_TIMESTAMP = "timestamp";
     /**
      * A {@link SliceItem} that contains a {@link RemoteInput}.
      */
-    public static final int TYPE_REMOTE_INPUT = 9;
+    public static final String FORMAT_REMOTE_INPUT = "input";
 
     /**
      * @hide
      */
     protected @Slice.SliceHint
     String[] mHints;
-    private final int mType;
+    private final String mFormat;
+    private final String mSubType;
     private final Object mObj;
 
     /**
      * @hide
      */
-    public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
+    public SliceItem(Object obj, @SliceType String format, String subType,
+            @Slice.SliceHint String[] hints) {
         mHints = hints;
-        mType = type;
+        mFormat = format;
+        mSubType = subType;
         mObj = obj;
     }
 
     /**
      * @hide
      */
-    public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
-        this(new Pair<>(intent, slice), type, hints);
+    public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
+            @Slice.SliceHint String[] hints) {
+        this(new Pair<>(intent, slice), format, subType, hints);
     }
 
     /**
@@ -141,26 +141,51 @@
         ArrayUtils.removeElement(String.class, mHints, hint);
     }
 
-    public @SliceType int getType() {
-        return mType;
+    /**
+     * Get the format of this SliceItem.
+     * <p>
+     * The format will be one of the following types supported by the platform:
+     * <li>{@link #FORMAT_SLICE}</li>
+     * <li>{@link #FORMAT_TEXT}</li>
+     * <li>{@link #FORMAT_IMAGE}</li>
+     * <li>{@link #FORMAT_ACTION}</li>
+     * <li>{@link #FORMAT_COLOR}</li>
+     * <li>{@link #FORMAT_TIMESTAMP}</li>
+     * <li>{@link #FORMAT_REMOTE_INPUT}</li>
+     * @see #getSubType() ()
+     */
+    public String getFormat() {
+        return mFormat;
     }
 
     /**
-     * @return The text held by this {@link #TYPE_TEXT} SliceItem
+     * Get the sub-type of this SliceItem.
+     * <p>
+     * Subtypes provide additional information about the type of this information beyond basic
+     * interpretations inferred by {@link #getFormat()}. For example a slice may contain
+     * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}.
+     * @see #getFormat()
+     */
+    public String getSubType() {
+        return mSubType;
+    }
+
+    /**
+     * @return The text held by this {@link #FORMAT_TEXT} SliceItem
      */
     public CharSequence getText() {
         return (CharSequence) mObj;
     }
 
     /**
-     * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+     * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem
      */
     public Icon getIcon() {
         return (Icon) mObj;
     }
 
     /**
-     * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+     * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem
      */
     public PendingIntent getAction() {
         return ((Pair<PendingIntent, Slice>) mObj).first;
@@ -174,31 +199,31 @@
     }
 
     /**
-     * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+     * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem
      */
     public RemoteInput getRemoteInput() {
         return (RemoteInput) mObj;
     }
 
     /**
-     * @return The color held by this {@link #TYPE_COLOR} SliceItem
+     * @return The color held by this {@link #FORMAT_COLOR} SliceItem
      */
     public int getColor() {
         return (Integer) mObj;
     }
 
     /**
-     * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+     * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
      */
     public Slice getSlice() {
-        if (getType() == TYPE_ACTION) {
+        if (getFormat() == FORMAT_ACTION) {
             return ((Pair<PendingIntent, Slice>) mObj).second;
         }
         return (Slice) mObj;
     }
 
     /**
-     * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+     * @return The timestamp held by this {@link #FORMAT_TIMESTAMP} SliceItem
      */
     public long getTimestamp() {
         return (Long) mObj;
@@ -217,8 +242,9 @@
      */
     public SliceItem(Parcel in) {
         mHints = in.readStringArray();
-        mType = in.readInt();
-        mObj = readObj(mType, in);
+        mFormat = in.readString();
+        mSubType = in.readString();
+        mObj = readObj(mFormat, in);
     }
 
     @Override
@@ -229,8 +255,9 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeStringArray(mHints);
-        dest.writeInt(mType);
-        writeObj(dest, flags, mObj, mType);
+        dest.writeString(mFormat);
+        dest.writeString(mSubType);
+        writeObj(dest, flags, mObj, mFormat);
     }
 
     /**
@@ -259,49 +286,54 @@
         return false;
     }
 
-    private void writeObj(Parcel dest, int flags, Object obj, int type) {
-        switch (type) {
-            case TYPE_SLICE:
-            case TYPE_REMOTE_VIEW:
-            case TYPE_IMAGE:
-            case TYPE_REMOTE_INPUT:
+    private static String getBaseType(String type) {
+        int index = type.indexOf('/');
+        if (index >= 0) {
+            return type.substring(0, index);
+        }
+        return type;
+    }
+
+    private static void writeObj(Parcel dest, int flags, Object obj, String type) {
+        switch (getBaseType(type)) {
+            case FORMAT_SLICE:
+            case FORMAT_IMAGE:
+            case FORMAT_REMOTE_INPUT:
                 ((Parcelable) obj).writeToParcel(dest, flags);
                 break;
-            case TYPE_ACTION:
+            case FORMAT_ACTION:
                 ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
                 ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
                 break;
-            case TYPE_TEXT:
-                TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+            case FORMAT_TEXT:
+                TextUtils.writeToParcel((CharSequence) obj, dest, flags);
                 break;
-            case TYPE_COLOR:
-                dest.writeInt((Integer) mObj);
+            case FORMAT_COLOR:
+                dest.writeInt((Integer) obj);
                 break;
-            case TYPE_TIMESTAMP:
-                dest.writeLong((Long) mObj);
+            case FORMAT_TIMESTAMP:
+                dest.writeLong((Long) obj);
                 break;
         }
     }
 
-    private static Object readObj(int type, Parcel in) {
-        switch (type) {
-            case TYPE_SLICE:
+    private static Object readObj(String type, Parcel in) {
+        switch (getBaseType(type)) {
+            case FORMAT_SLICE:
                 return Slice.CREATOR.createFromParcel(in);
-            case TYPE_TEXT:
+            case FORMAT_TEXT:
                 return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-            case TYPE_IMAGE:
+            case FORMAT_IMAGE:
                 return Icon.CREATOR.createFromParcel(in);
-            case TYPE_ACTION:
-                return new Pair<PendingIntent, Slice>(
+            case FORMAT_ACTION:
+                return new Pair<>(
                         PendingIntent.CREATOR.createFromParcel(in),
                         Slice.CREATOR.createFromParcel(in));
-            case TYPE_REMOTE_VIEW:
-                return RemoteViews.CREATOR.createFromParcel(in);
-            case TYPE_COLOR:
+            case FORMAT_COLOR:
                 return in.readInt();
-            case TYPE_TIMESTAMP:
+            case FORMAT_TIMESTAMP:
                 return in.readLong();
-            case TYPE_REMOTE_INPUT:
+            case FORMAT_REMOTE_INPUT:
                 return RemoteInput.CREATOR.createFromParcel(in);
         }
         throw new RuntimeException("Unsupported type " + type);
@@ -318,29 +350,4 @@
             return new SliceItem[size];
         }
     };
-
-    /**
-     * @hide
-     */
-    public static String typeToString(int type) {
-        switch (type) {
-            case TYPE_SLICE:
-                return "Slice";
-            case TYPE_TEXT:
-                return "Text";
-            case TYPE_IMAGE:
-                return "Image";
-            case TYPE_ACTION:
-                return "Action";
-            case TYPE_REMOTE_VIEW:
-                return "RemoteView";
-            case TYPE_COLOR:
-                return "Color";
-            case TYPE_TIMESTAMP:
-                return "Timestamp";
-            case TYPE_REMOTE_INPUT:
-                return "RemoteInput";
-        }
-        return "Unrecognized type: " + type;
-    }
 }
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 05f4ce6..ac5365c 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -17,7 +17,6 @@
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
-import android.app.slice.widget.SliceView;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -37,6 +36,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -93,6 +93,10 @@
     /**
      * @hide
      */
+    public static final String EXTRA_SUPPORTED_SPECS = "supported_specs";
+    /**
+     * @hide
+     */
     public static final String METHOD_SLICE = "bind_slice";
     /**
      * @hide
@@ -118,12 +122,25 @@
      * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
      * when the app is ready to provide the complete data in onBindSlice.
      * <p>
+     * The slice returned should have a spec that is compatible with one of
+     * the supported specs.
      *
+     * @param sliceUri Uri to bind.
+     * @param supportedSpecs List of supported specs.
      * @see {@link Slice}.
      * @see {@link Slice#HINT_PARTIAL}
      */
-    // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
-    public abstract Slice onBindSlice(Uri sliceUri);
+    public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+        return onBindSlice(sliceUri);
+    }
+
+    /**
+     * @deprecated migrating to {@link #onBindSlice(Uri, List)}
+     */
+    @Deprecated
+    public Slice onBindSlice(Uri sliceUri) {
+        return null;
+    }
 
     /**
      * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
@@ -132,7 +149,6 @@
      *
      * @return Uri representing the slice associated with the provided intent.
      * @see {@link Slice}
-     * @see {@link SliceView#setSlice(Intent)}
      */
     public @NonNull Uri onMapIntentToUri(Intent intent) {
         throw new UnsupportedOperationException(
@@ -195,8 +211,9 @@
                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                         "Slice binding requires the permission BIND_SLICE");
             }
+            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
 
-            Slice s = handleBindSlice(uri);
+            Slice s = handleBindSlice(uri, supportedSpecs);
             Bundle b = new Bundle();
             b.putParcelable(EXTRA_SLICE, s);
             return b;
@@ -205,9 +222,10 @@
                     "Slice binding requires the permission BIND_SLICE");
             Intent intent = extras.getParcelable(EXTRA_INTENT);
             Uri uri = onMapIntentToUri(intent);
+            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
             Bundle b = new Bundle();
             if (uri != null) {
-                Slice s = handleBindSlice(uri);
+                Slice s = handleBindSlice(uri, supportedSpecs);
                 b.putParcelable(EXTRA_SLICE, s);
             } else {
                 b.putParcelable(EXTRA_SLICE, null);
@@ -217,14 +235,14 @@
         return super.call(method, arg, extras);
     }
 
-    private Slice handleBindSlice(Uri sliceUri) {
+    private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
         if (Looper.myLooper() == Looper.getMainLooper()) {
-            return onBindSliceStrict(sliceUri);
+            return onBindSliceStrict(sliceUri, supportedSpecs);
         } else {
             CountDownLatch latch = new CountDownLatch(1);
             Slice[] output = new Slice[1];
             Handler.getMain().post(() -> {
-                output[0] = onBindSliceStrict(sliceUri);
+                output[0] = onBindSliceStrict(sliceUri, supportedSpecs);
                 latch.countDown();
             });
             try {
@@ -236,14 +254,14 @@
         }
     }
 
-    private Slice onBindSliceStrict(Uri sliceUri) {
+    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
         ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
         try {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectAll()
                     .penaltyDeath()
                     .build());
-            return onBindSlice(sliceUri);
+            return onBindSlice(sliceUri, supportedSpecs);
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
         }
diff --git a/core/java/android/app/slice/SliceQuery.java b/core/java/android/app/slice/SliceQuery.java
index 9943c49..20eca88 100644
--- a/core/java/android/app/slice/SliceQuery.java
+++ b/core/java/android/app/slice/SliceQuery.java
@@ -19,6 +19,7 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Queue;
 import java.util.Spliterators;
 import java.util.stream.Collectors;
@@ -37,14 +38,15 @@
      */
     public static SliceItem getPrimaryIcon(Slice slice) {
         for (SliceItem item : slice.getItems()) {
-            if (item.getType() == SliceItem.TYPE_IMAGE) {
+            if (Objects.equals(item.getFormat(), SliceItem.FORMAT_IMAGE)) {
                 return item;
             }
-            if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+            if (!(compareTypes(item, SliceItem.FORMAT_SLICE)
+                    && item.hasHint(Slice.HINT_LIST))
                     && !item.hasHint(Slice.HINT_ACTIONS)
                     && !item.hasHint(Slice.HINT_LIST_ITEM)
-                    && (item.getType() != SliceItem.TYPE_ACTION)) {
-                SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+                    && !compareTypes(item, SliceItem.FORMAT_ACTION)) {
+                SliceItem icon = SliceQuery.find(item, SliceItem.FORMAT_IMAGE);
                 if (icon != null) {
                     return icon;
                 }
@@ -78,23 +80,23 @@
     /**
      * @hide
      */
-    public static List<SliceItem> findAll(SliceItem s, int type) {
+    public static List<SliceItem> findAll(SliceItem s, String type) {
         return findAll(s, type, (String[]) null, null);
     }
 
     /**
      * @hide
      */
-    public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+    public static List<SliceItem> findAll(SliceItem s, String type, String hints, String nonHints) {
         return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
     }
 
     /**
      * @hide
      */
-    public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+    public static List<SliceItem> findAll(SliceItem s, String type, String[] hints,
             String[] nonHints) {
-        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+        return stream(s).filter(item -> compareTypes(item, type)
                 && (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
                 .collect(Collectors.toList());
     }
@@ -102,45 +104,45 @@
     /**
      * @hide
      */
-    public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+    public static SliceItem find(Slice s, String type, String hints, String nonHints) {
         return find(s, type, new String[]{ hints }, new String[]{ nonHints });
     }
 
     /**
      * @hide
      */
-    public static SliceItem find(Slice s, int type) {
+    public static SliceItem find(Slice s, String type) {
         return find(s, type, (String[]) null, null);
     }
 
     /**
      * @hide
      */
-    public static SliceItem find(SliceItem s, int type) {
+    public static SliceItem find(SliceItem s, String type) {
         return find(s, type, (String[]) null, null);
     }
 
     /**
      * @hide
      */
-    public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+    public static SliceItem find(SliceItem s, String type, String hints, String nonHints) {
         return find(s, type, new String[]{ hints }, new String[]{ nonHints });
     }
 
     /**
      * @hide
      */
-    public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+    public static SliceItem find(Slice s, String type, String[] hints, String[] nonHints) {
         List<String> h = s.getHints();
-        return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
-                hints, nonHints);
+        return find(new SliceItem(s, SliceItem.FORMAT_SLICE, null, h.toArray(new String[h.size()])),
+                type, hints, nonHints);
     }
 
     /**
      * @hide
      */
-    public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
-        return stream(s).filter(item -> (item.getType() == type || type == -1)
+    public static SliceItem find(SliceItem s, String type, String[] hints, String[] nonHints) {
+        return stream(s).filter(item -> compareTypes(item, type)
                 && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
     }
 
@@ -159,8 +161,8 @@
             @Override
             public SliceItem next() {
                 SliceItem item = items.poll();
-                if (item.getType() == SliceItem.TYPE_SLICE
-                        || item.getType() == SliceItem.TYPE_ACTION) {
+                if (compareTypes(item, SliceItem.FORMAT_SLICE)
+                        || compareTypes(item, SliceItem.FORMAT_ACTION)) {
                     items.addAll(item.getSlice().getItems());
                 }
                 return item;
@@ -168,4 +170,19 @@
         };
         return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
     }
+
+    /**
+     * @hide
+     */
+    public static boolean compareTypes(SliceItem item, String desiredType) {
+        final int typeLength = desiredType.length();
+        if (typeLength == 3 && desiredType.equals("*/*")) {
+            return true;
+        }
+        if (item.getSubType() == null && desiredType.indexOf('/') < 0) {
+            return item.getFormat().equals(desiredType);
+        }
+        return (item.getFormat() + "/" + item.getSubType())
+                .matches(desiredType.replaceAll("\\*", ".*"));
+    }
 }
diff --git a/core/java/android/app/slice/SliceSpec.java b/core/java/android/app/slice/SliceSpec.java
new file mode 100644
index 0000000..433b67e
--- /dev/null
+++ b/core/java/android/app/slice/SliceSpec.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.slice;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class describing the structure of the data contained within a slice.
+ * <p>
+ * A data version contains a string which describes the type of structure
+ * and a revision which denotes this specific implementation. Revisions are expected
+ * to be backwards compatible and monotonically increasing. Meaning if a
+ * SliceSpec has the same type and an equal or lesser revision,
+ * it is expected to be compatible.
+ * <p>
+ * Apps rendering slices will provide a list of supported versions to the OS which
+ * will also be given to the app. Apps should only return a {@link Slice} with a
+ * {@link SliceSpec} that one of the supported {@link SliceSpec}s provided
+ * {@link #canRender}.
+ *
+ * @see Slice
+ * @see SliceProvider#onBindSlice(Uri)
+ */
+public final class SliceSpec implements Parcelable {
+
+    private final String mType;
+    private final int mRevision;
+
+    public SliceSpec(@NonNull String type, int revision) {
+        mType = type;
+        mRevision = revision;
+    }
+
+    /**
+     * @hide
+     */
+    public SliceSpec(Parcel source) {
+        mType = source.readString();
+        mRevision = source.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mType);
+        dest.writeInt(mRevision);
+    }
+
+    /**
+     * Gets the type of the version.
+     */
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Gets the revision of the version.
+     */
+    public int getRevision() {
+        return mRevision;
+    }
+
+    /**
+     * Indicates that this spec can be used to render the specified spec.
+     * <p>
+     * Rendering support is not bi-directional (e.g. Spec v3 can render
+     * Spec v2, but Spec v2 cannot render Spec v3).
+     *
+     * @param candidate candidate format of data.
+     * @return true if versions are compatible.
+     * @see androidx.app.slice.widget.SliceView
+     */
+    public boolean canRender(@NonNull SliceSpec candidate) {
+        if (!mType.equals(candidate.mType)) return false;
+        return mRevision >= candidate.mRevision;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SliceSpec)) return false;
+        SliceSpec other = (SliceSpec) obj;
+        return mType.equals(other.mType) && mRevision == other.mRevision;
+    }
+
+    public static final Creator<SliceSpec> CREATOR = new Creator<SliceSpec>() {
+        @Override
+        public SliceSpec createFromParcel(Parcel source) {
+            return new SliceSpec(source);
+        }
+
+        @Override
+        public SliceSpec[] newArray(int size) {
+            return new SliceSpec[size];
+        }
+    };
+}
diff --git a/core/java/android/app/slice/widget/ActionRow.java b/core/java/android/app/slice/widget/ActionRow.java
deleted file mode 100644
index c96e6304..0000000
--- a/core/java/android/app/slice/widget/ActionRow.java
+++ /dev/null
@@ -1,201 +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.
- */
-
-package android.app.slice.widget;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteInput;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewParent;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class ActionRow extends FrameLayout {
-
-    private static final int MAX_ACTIONS = 5;
-    private final int mSize;
-    private final int mIconPadding;
-    private final LinearLayout mActionsGroup;
-    private final boolean mFullActions;
-    private int mColor = Color.BLACK;
-
-    public ActionRow(Context context, boolean fullActions) {
-        super(context);
-        mFullActions = fullActions;
-        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
-                context.getResources().getDisplayMetrics());
-        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
-                context.getResources().getDisplayMetrics());
-        mActionsGroup = new LinearLayout(context);
-        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
-        mActionsGroup.setLayoutParams(
-                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
-        addView(mActionsGroup);
-    }
-
-    private void setColor(int color) {
-        mColor = color;
-        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
-            View view = mActionsGroup.getChildAt(i);
-            SliceItem item = (SliceItem) view.getTag();
-            boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
-            if (tint) {
-                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
-            }
-        }
-    }
-
-    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
-        ImageView imageView = new ImageView(getContext());
-        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
-        imageView.setScaleType(ScaleType.FIT_CENTER);
-        imageView.setImageIcon(icon);
-        if (allowTint) {
-            imageView.setImageTintList(ColorStateList.valueOf(mColor));
-        }
-        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
-                android.R.attr.selectableItemBackground));
-        imageView.setTag(image);
-        addAction(imageView);
-        return imageView;
-    }
-
-    /**
-     * Set the actions and color for this action row.
-     */
-    public void setActions(SliceItem actionRow, SliceItem defColor) {
-        removeAllViews();
-        mActionsGroup.removeAllViews();
-        addView(mActionsGroup);
-
-        SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
-        if (color == null) {
-            color = defColor;
-        }
-        if (color != null) {
-            setColor(color.getColor());
-        }
-        SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
-            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
-                return;
-            }
-            SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
-            if (image == null) {
-                return;
-            }
-            boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
-            SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
-            if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
-                addAction(image.getIcon(), tint, image).setOnClickListener(
-                        v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
-                createRemoteInputView(mColor, getContext());
-            } else {
-                addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
-                        () -> {
-                            try {
-                                action.getAction().send();
-                            } catch (CanceledException e) {
-                                e.printStackTrace();
-                            }
-                        }));
-            }
-        });
-        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
-    }
-
-    private void addAction(View child) {
-        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
-    }
-
-    private void createRemoteInputView(int color, Context context) {
-        View riv = RemoteInputView.inflate(context, this);
-        riv.setVisibility(View.INVISIBLE);
-        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        riv.setBackgroundColor(color);
-    }
-
-    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
-            RemoteInput input) {
-        if (input == null) {
-            return false;
-        }
-
-        ViewParent p = view.getParent().getParent();
-        RemoteInputView riv = null;
-        while (p != null) {
-            if (p instanceof View) {
-                View pv = (View) p;
-                riv = findRemoteInputView(pv);
-                if (riv != null) {
-                    break;
-                }
-            }
-            p = p.getParent();
-        }
-        if (riv == null) {
-            return false;
-        }
-
-        int width = view.getWidth();
-        if (view instanceof TextView) {
-            // Center the reveal on the text which might be off-center from the TextView
-            TextView tv = (TextView) view;
-            if (tv.getLayout() != null) {
-                int innerWidth = (int) tv.getLayout().getLineWidth(0);
-                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
-                width = Math.min(width, innerWidth);
-            }
-        }
-        int cx = view.getLeft() + width / 2;
-        int cy = view.getTop() + view.getHeight() / 2;
-        int w = riv.getWidth();
-        int h = riv.getHeight();
-        int r = Math.max(
-                Math.max(cx + cy, cx + (h - cy)),
-                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
-        riv.setRevealParameters(cx, cy, r);
-        riv.setPendingIntent(pendingIntent);
-        riv.setRemoteInput(new RemoteInput[] {
-                input
-        }, input);
-        riv.focusAnimated();
-        return true;
-    }
-
-    private RemoteInputView findRemoteInputView(View v) {
-        if (v == null) {
-            return null;
-        }
-        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
-    }
-}
diff --git a/core/java/android/app/slice/widget/GridView.java b/core/java/android/app/slice/widget/GridView.java
deleted file mode 100644
index 793abc0..0000000
--- a/core/java/android/app/slice/widget/GridView.java
+++ /dev/null
@@ -1,192 +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.
- */
-
-package android.app.slice.widget;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Color;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class GridView extends LinearLayout implements SliceListView {
-
-    private static final String TAG = "GridView";
-
-    private static final int MAX_IMAGES = 3;
-    private static final int MAX_ALL = 5;
-    private boolean mIsAllImages;
-
-    public GridView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mIsAllImages) {
-            int width = MeasureSpec.getSize(widthMeasureSpec);
-            int height = width / getChildCount();
-            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
-                    height);
-            getLayoutParams().height = height;
-            for (int i = 0; i < getChildCount(); i++) {
-                getChildAt(i).getLayoutParams().height = height;
-            }
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    public void setSliceItem(SliceItem slice) {
-        mIsAllImages = true;
-        removeAllViews();
-        int total = 1;
-        if (slice.getType() == SliceItem.TYPE_SLICE) {
-            List<SliceItem> items = slice.getSlice().getItems();
-            total = items.size();
-            for (int i = 0; i < total; i++) {
-                SliceItem item = items.get(i);
-                if (isFull()) {
-                    continue;
-                }
-                if (!addItem(item)) {
-                    mIsAllImages = false;
-                }
-            }
-        } else {
-            if (!isFull()) {
-                if (!addItem(slice)) {
-                    mIsAllImages = false;
-                }
-            }
-        }
-        if (total > getChildCount() && mIsAllImages) {
-            addExtraCount(total - getChildCount());
-        }
-    }
-
-    private void addExtraCount(int numExtra) {
-        View last = getChildAt(getChildCount() - 1);
-        FrameLayout frame = new FrameLayout(getContext());
-        frame.setLayoutParams(last.getLayoutParams());
-
-        removeView(last);
-        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
-        TextView v = new TextView(getContext());
-        v.setTextColor(Color.WHITE);
-        v.setBackgroundColor(0x4d000000);
-        v.setText(getResources().getString(R.string.slice_more_content, numExtra));
-        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
-        v.setGravity(Gravity.CENTER);
-        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
-        addView(frame);
-    }
-
-    private boolean isFull() {
-        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
-    }
-
-    /**
-     * Returns true if this item is just an image.
-     */
-    private boolean addItem(SliceItem item) {
-        if (item.hasHint(Slice.HINT_HIDDEN)) {
-            return false;
-        }
-        if (item.getType() == SliceItem.TYPE_IMAGE) {
-            ImageView v = new ImageView(getContext());
-            v.setImageIcon(item.getIcon());
-            v.setScaleType(ScaleType.CENTER_CROP);
-            addView(v, new LayoutParams(0, MATCH_PARENT, 1));
-            return true;
-        } else {
-            LinearLayout v = new LinearLayout(getContext());
-            int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                    12, getContext().getResources().getDisplayMetrics());
-            v.setPadding(0, s, 0, 0);
-            v.setOrientation(LinearLayout.VERTICAL);
-            v.setGravity(Gravity.CENTER_HORIZONTAL);
-            // TODO: Unify sporadic inflates that happen throughout the code.
-            ArrayList<SliceItem> items = new ArrayList<>();
-            if (item.getType() == SliceItem.TYPE_SLICE) {
-                items.addAll(item.getSlice().getItems());
-            }
-            items.forEach(i -> {
-                if (i.hasHint(Slice.HINT_HIDDEN)) {
-                    return;
-                }
-                Context context = getContext();
-                switch (i.getType()) {
-                    case SliceItem.TYPE_TEXT:
-                        boolean title = false;
-                        if ((item.hasAnyHints(new String[] {
-                                Slice.HINT_LARGE, Slice.HINT_TITLE
-                        }))) {
-                            title = true;
-                        }
-                        TextView tv = (TextView) LayoutInflater.from(context).inflate(
-                                title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
-                        tv.setText(i.getText());
-                        v.addView(tv);
-                        break;
-                    case SliceItem.TYPE_IMAGE:
-                        ImageView iv = new ImageView(context);
-                        iv.setImageIcon(i.getIcon());
-                        if (item.hasHint(Slice.HINT_LARGE)) {
-                            iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-                        } else {
-                            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                                    48, context.getResources().getDisplayMetrics());
-                            iv.setLayoutParams(new LayoutParams(size, size));
-                        }
-                        v.addView(iv);
-                        break;
-                    case SliceItem.TYPE_REMOTE_VIEW:
-                        v.addView(i.getRemoteView().apply(context, v));
-                        break;
-                    case SliceItem.TYPE_COLOR:
-                        // TODO: Support color to tint stuff here.
-                        break;
-                }
-            });
-            addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
-            return false;
-        }
-    }
-}
diff --git a/core/java/android/app/slice/widget/LargeSliceAdapter.java b/core/java/android/app/slice/widget/LargeSliceAdapter.java
deleted file mode 100644
index 267fff6..0000000
--- a/core/java/android/app/slice/widget/LargeSliceAdapter.java
+++ /dev/null
@@ -1,224 +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.
- */
-
-package android.app.slice.widget;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
-import android.content.Context;
-import android.util.ArrayMap;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.widget.RecyclerView;
-import com.android.internal.widget.RecyclerView.ViewHolder;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * @hide
- */
-public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
-
-    public static final int TYPE_DEFAULT       = 1;
-    public static final int TYPE_HEADER        = 2;
-    public static final int TYPE_GRID          = 3;
-    public static final int TYPE_MESSAGE       = 4;
-    public static final int TYPE_MESSAGE_LOCAL = 5;
-    public static final int TYPE_REMOTE_VIEWS  = 6;
-
-    private final IdGenerator mIdGen = new IdGenerator();
-    private final Context mContext;
-    private List<SliceWrapper> mSlices = new ArrayList<>();
-    private SliceItem mColor;
-
-    public LargeSliceAdapter(Context context) {
-        mContext = context;
-        setHasStableIds(true);
-    }
-
-    /**
-     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
-     */
-    public void setSliceItems(List<SliceItem> slices, SliceItem color) {
-        mColor = color;
-        mIdGen.resetUsage();
-        mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
-                .collect(Collectors.toList());
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View v = inflateForType(viewType);
-        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
-        return new SliceViewHolder(v);
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        return mSlices.get(position).mType;
-    }
-
-    @Override
-    public long getItemId(int position) {
-        return mSlices.get(position).mId;
-    }
-
-    @Override
-    public int getItemCount() {
-        return mSlices.size();
-    }
-
-    @Override
-    public void onBindViewHolder(SliceViewHolder holder, int position) {
-        SliceWrapper slice = mSlices.get(position);
-        if (holder.mSliceView != null) {
-            holder.mSliceView.setColor(mColor);
-            holder.mSliceView.setSliceItem(slice.mItem);
-        } else if (slice.mType == TYPE_REMOTE_VIEWS) {
-            FrameLayout frame = (FrameLayout) holder.itemView;
-            frame.removeAllViews();
-            frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
-        }
-    }
-
-    private View inflateForType(int viewType) {
-        switch (viewType) {
-            case TYPE_REMOTE_VIEWS:
-                return new FrameLayout(mContext);
-            case TYPE_GRID:
-                return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
-            case TYPE_MESSAGE:
-                return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
-            case TYPE_MESSAGE_LOCAL:
-                return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
-        }
-        return new SmallTemplateView(mContext);
-    }
-
-    protected static class SliceWrapper {
-        private final SliceItem mItem;
-        private final int mType;
-        private final long mId;
-
-        public SliceWrapper(SliceItem item, IdGenerator idGen) {
-            mItem = item;
-            mType = getType(item);
-            mId = idGen.getId(item);
-        }
-
-        public static int getType(SliceItem item) {
-            if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
-                return TYPE_REMOTE_VIEWS;
-            }
-            if (item.hasHint(Slice.HINT_MESSAGE)) {
-                // TODO: Better way to determine me or not? Something more like Messaging style.
-                if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
-                    return TYPE_MESSAGE;
-                } else {
-                    return TYPE_MESSAGE_LOCAL;
-                }
-            }
-            if (item.hasHint(Slice.HINT_HORIZONTAL)) {
-                return TYPE_GRID;
-            }
-            return TYPE_DEFAULT;
-        }
-    }
-
-    /**
-     * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
-     */
-    public static class SliceViewHolder extends ViewHolder {
-        public final SliceListView mSliceView;
-
-        public SliceViewHolder(View itemView) {
-            super(itemView);
-            mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
-        }
-    }
-
-    /**
-     * View slices being displayed in {@link LargeSliceAdapter}.
-     */
-    public interface SliceListView {
-        /**
-         * Set the slice item for this view.
-         */
-        void setSliceItem(SliceItem slice);
-
-        /**
-         * Set the color for the items in this view.
-         */
-        default void setColor(SliceItem color) {
-
-        }
-    }
-
-    private static class IdGenerator {
-        private long mNextLong = 0;
-        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
-        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
-
-        public long getId(SliceItem item) {
-            String str = genString(item);
-            if (!mCurrentIds.containsKey(str)) {
-                mCurrentIds.put(str, mNextLong++);
-            }
-            long id = mCurrentIds.get(str);
-            int index = mUsedIds.getOrDefault(str, 0);
-            mUsedIds.put(str, index + 1);
-            return id + index * 10000;
-        }
-
-        private String genString(SliceItem item) {
-            StringBuilder builder = new StringBuilder();
-            SliceQuery.stream(item).forEach(i -> {
-                builder.append(i.getType());
-                i.removeHint(Slice.HINT_SELECTED);
-                builder.append(i.getHints());
-                switch (i.getType()) {
-                    case SliceItem.TYPE_REMOTE_VIEW:
-                        builder.append(i.getRemoteView());
-                        break;
-                    case SliceItem.TYPE_IMAGE:
-                        builder.append(i.getIcon());
-                        break;
-                    case SliceItem.TYPE_TEXT:
-                        builder.append(i.getText());
-                        break;
-                    case SliceItem.TYPE_COLOR:
-                        builder.append(i.getColor());
-                        break;
-                }
-            });
-            return builder.toString();
-        }
-
-        public void resetUsage() {
-            mUsedIds.clear();
-        }
-    }
-}
diff --git a/core/java/android/app/slice/widget/LargeTemplateView.java b/core/java/android/app/slice/widget/LargeTemplateView.java
deleted file mode 100644
index 788f6fb..0000000
--- a/core/java/android/app/slice/widget/LargeTemplateView.java
+++ /dev/null
@@ -1,131 +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.
- */
-
-package android.app.slice.widget;
-
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.util.TypedValue;
-
-import com.android.internal.widget.LinearLayoutManager;
-import com.android.internal.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class LargeTemplateView extends SliceModeView {
-
-    private final LargeSliceAdapter mAdapter;
-    private final RecyclerView mRecyclerView;
-    private final int mDefaultHeight;
-    private final int mMaxHeight;
-    private Slice mSlice;
-    private boolean mIsScrollable;
-
-    public LargeTemplateView(Context context) {
-        super(context);
-
-        mRecyclerView = new RecyclerView(getContext());
-        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-        mAdapter = new LargeSliceAdapter(context);
-        mRecyclerView.setAdapter(mAdapter);
-        addView(mRecyclerView);
-        mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
-                getResources().getDisplayMetrics());
-        mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
-                getResources().getDisplayMetrics());
-    }
-
-    @Override
-    public String getMode() {
-        return SliceView.MODE_LARGE;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (mRecyclerView.getMeasuredHeight() > mMaxHeight
-                || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
-            mRecyclerView.getLayoutParams().height = mDefaultHeight;
-        } else {
-            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
-    public void setSlice(Slice slice) {
-        SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
-        mSlice = slice;
-        List<SliceItem> items = new ArrayList<>();
-        boolean[] hasHeader = new boolean[1];
-        if (slice.hasHint(Slice.HINT_LIST)) {
-            addList(slice, items);
-        } else {
-            slice.getItems().forEach(item -> {
-                if (item.hasHint(Slice.HINT_HIDDEN)) {
-                    // If it's hidden we don't show it
-                    return;
-                } else if (item.hasHint(Slice.HINT_ACTIONS)) {
-                    // Action groups don't show in lists
-                    return;
-                } else if (item.getType() == SliceItem.TYPE_COLOR) {
-                    // A color is not a list item
-                    return;
-                } else if (item.getType() == SliceItem.TYPE_SLICE
-                        && item.hasHint(Slice.HINT_LIST)) {
-                    addList(item.getSlice(), items);
-                } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
-                    items.add(item);
-                } else if (!hasHeader[0]) {
-                    hasHeader[0] = true;
-                    items.add(0, item);
-                } else {
-                    item.addHint(Slice.HINT_LIST_ITEM);
-                    items.add(item);
-                }
-            });
-        }
-        mAdapter.setSliceItems(items, color);
-    }
-
-    private void addList(Slice slice, List<SliceItem> items) {
-        List<SliceItem> sliceItems = slice.getItems();
-        sliceItems.forEach(i -> {
-            if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) {
-                i.addHint(Slice.HINT_LIST_ITEM);
-                items.add(i);
-            }
-        });
-    }
-
-    /**
-     * Whether or not the content in this template should be scrollable.
-     */
-    public void setScrollable(boolean isScrollable) {
-        // TODO -- restrict / enable how much this view can show
-        mIsScrollable = isScrollable;
-    }
-}
diff --git a/core/java/android/app/slice/widget/MessageView.java b/core/java/android/app/slice/widget/MessageView.java
deleted file mode 100644
index 3124398..0000000
--- a/core/java/android/app/slice/widget/MessageView.java
+++ /dev/null
@@ -1,77 +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.
- */
-
-package android.app.slice.widget;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class MessageView extends LinearLayout implements SliceListView {
-
-    private TextView mDetails;
-    private ImageView mIcon;
-
-    public MessageView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mDetails = findViewById(android.R.id.summary);
-        mIcon = findViewById(android.R.id.icon);
-    }
-
-    @Override
-    public void setSliceItem(SliceItem slice) {
-        SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
-        if (source != null) {
-            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                    24, getContext().getResources().getDisplayMetrics());
-            // TODO try and turn this into a drawable
-            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
-            Canvas iconCanvas = new Canvas(iconBm);
-            Drawable d = source.getIcon().loadDrawable(getContext());
-            d.setBounds(0, 0, iconSize, iconSize);
-            d.draw(iconCanvas);
-            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
-        }
-        SpannableStringBuilder builder = new SpannableStringBuilder();
-        SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
-            if (builder.length() != 0) {
-                builder.append('\n');
-            }
-            builder.append(text.getText());
-        });
-        mDetails.setText(builder.toString());
-    }
-
-}
diff --git a/core/java/android/app/slice/widget/RemoteInputView.java b/core/java/android/app/slice/widget/RemoteInputView.java
deleted file mode 100644
index 6eff5af..0000000
--- a/core/java/android/app/slice/widget/RemoteInputView.java
+++ /dev/null
@@ -1,445 +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.
- */
-
-package android.app.slice.widget;
-
-import android.animation.Animator;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutManager;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.R;
-
-/**
- * Host for the remote input.
- *
- * @hide
- */
-// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
-public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
-
-    private static final String TAG = "RemoteInput";
-
-    /**
-     * A marker object that let's us easily find views of this class.
-     */
-    public static final Object VIEW_TAG = new Object();
-
-    private RemoteEditText mEditText;
-    private ImageButton mSendButton;
-    private ProgressBar mProgressBar;
-    private PendingIntent mPendingIntent;
-    private RemoteInput[] mRemoteInputs;
-    private RemoteInput mRemoteInput;
-
-    private int mRevealCx;
-    private int mRevealCy;
-    private int mRevealR;
-    private boolean mResetting;
-
-    public RemoteInputView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mProgressBar = findViewById(R.id.remote_input_progress);
-        mSendButton = findViewById(R.id.remote_input_send);
-        mSendButton.setOnClickListener(this);
-
-        mEditText = (RemoteEditText) getChildAt(0);
-        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
-            @Override
-            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-                final boolean isSoftImeEvent = event == null
-                        && (actionId == EditorInfo.IME_ACTION_DONE
-                                || actionId == EditorInfo.IME_ACTION_NEXT
-                                || actionId == EditorInfo.IME_ACTION_SEND);
-                final boolean isKeyboardEnterKey = event != null
-                        && KeyEvent.isConfirmKey(event.getKeyCode())
-                        && event.getAction() == KeyEvent.ACTION_DOWN;
-
-                if (isSoftImeEvent || isKeyboardEnterKey) {
-                    if (mEditText.length() > 0) {
-                        sendRemoteInput();
-                    }
-                    // Consume action to prevent IME from closing.
-                    return true;
-                }
-                return false;
-            }
-        });
-        mEditText.addTextChangedListener(this);
-        mEditText.setInnerFocusable(false);
-        mEditText.mRemoteInputView = this;
-    }
-
-    private void sendRemoteInput() {
-        Bundle results = new Bundle();
-        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
-        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
-                results);
-
-        mEditText.setEnabled(false);
-        mSendButton.setVisibility(INVISIBLE);
-        mProgressBar.setVisibility(VISIBLE);
-        mEditText.mShowImeOnInputConnection = false;
-
-        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
-        // will reset the throttling for this package.
-        // Strictly speaking, the intent receiver may be different from the intent creator,
-        // but that's an edge case, and also because we can't always know which package will receive
-        // an intent, so we just reset for the creator.
-        getContext().getSystemService(ShortcutManager.class).onApplicationActive(
-                mPendingIntent.getCreatorPackage(),
-                getContext().getUserId());
-
-        try {
-            mPendingIntent.send(mContext, 0, fillInIntent);
-            reset();
-        } catch (PendingIntent.CanceledException e) {
-            Log.i(TAG, "Unable to send remote input result", e);
-            Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
-                    Toast.LENGTH_SHORT).show();
-            reset();
-        }
-    }
-
-    /**
-     * Creates a remote input view.
-     */
-    public static RemoteInputView inflate(Context context, ViewGroup root) {
-        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
-                R.layout.slice_remote_input, root, false);
-        v.setTag(VIEW_TAG);
-        return v;
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v == mSendButton) {
-            sendRemoteInput();
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        super.onTouchEvent(event);
-
-        // We never want for a touch to escape to an outer view or one we covered.
-        return true;
-    }
-
-    private void onDefocus() {
-        setVisibility(INVISIBLE);
-    }
-
-    /**
-     * Set the pending intent for remote input.
-     */
-    public void setPendingIntent(PendingIntent pendingIntent) {
-        mPendingIntent = pendingIntent;
-    }
-
-    /**
-     * Set the remote inputs for this view.
-     */
-    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
-        mRemoteInputs = remoteInputs;
-        mRemoteInput = remoteInput;
-        mEditText.setHint(mRemoteInput.getLabel());
-    }
-
-    /**
-     * Focuses the remote input view.
-     */
-    public void focusAnimated() {
-        if (getVisibility() != VISIBLE) {
-            Animator animator = ViewAnimationUtils.createCircularReveal(
-                    this, mRevealCx, mRevealCy, 0, mRevealR);
-            animator.setDuration(200);
-            animator.start();
-        }
-        focus();
-    }
-
-    private void focus() {
-        setVisibility(VISIBLE);
-        mEditText.setInnerFocusable(true);
-        mEditText.mShowImeOnInputConnection = true;
-        mEditText.setSelection(mEditText.getText().length());
-        mEditText.requestFocus();
-        updateSendButton();
-    }
-
-    private void reset() {
-        mResetting = true;
-
-        mEditText.getText().clear();
-        mEditText.setEnabled(true);
-        mSendButton.setVisibility(VISIBLE);
-        mProgressBar.setVisibility(INVISIBLE);
-        updateSendButton();
-        onDefocus();
-
-        mResetting = false;
-    }
-
-    @Override
-    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
-        if (mResetting && child == mEditText) {
-            // Suppress text events if it happens during resetting. Ideally this would be
-            // suppressed by the text view not being shown, but that doesn't work here because it
-            // needs to stay visible for the animation.
-            return false;
-        }
-        return super.onRequestSendAccessibilityEvent(child, event);
-    }
-
-    private void updateSendButton() {
-        mSendButton.setEnabled(mEditText.getText().length() != 0);
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-        updateSendButton();
-    }
-
-    /**
-     * Tries to find an action that matches the current pending intent of this view and updates its
-     * state to that of the found action
-     *
-     * @return true if a matching action was found, false otherwise
-     */
-    public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
-        if (mPendingIntent == null || actions == null) {
-            return false;
-        }
-        Intent current = mPendingIntent.getIntent();
-        if (current == null) {
-            return false;
-        }
-
-        for (Notification.Action a : actions) {
-            RemoteInput[] inputs = a.getRemoteInputs();
-            if (a.actionIntent == null || inputs == null) {
-                continue;
-            }
-            Intent candidate = a.actionIntent.getIntent();
-            if (!current.filterEquals(candidate)) {
-                continue;
-            }
-
-            RemoteInput input = null;
-            for (RemoteInput i : inputs) {
-                if (i.getAllowFreeFormInput()) {
-                    input = i;
-                }
-            }
-            if (input == null) {
-                continue;
-            }
-            setPendingIntent(a.actionIntent);
-            setRemoteInput(inputs, input);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void setRevealParameters(int cx, int cy, int r) {
-        mRevealCx = cx;
-        mRevealCy = cy;
-        mRevealR = r;
-    }
-
-    @Override
-    public void dispatchStartTemporaryDetach() {
-        super.dispatchStartTemporaryDetach();
-        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
-        // won't lose IME focus.
-        detachViewFromParent(mEditText);
-    }
-
-    @Override
-    public void dispatchFinishTemporaryDetach() {
-        if (isAttachedToWindow()) {
-            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
-        } else {
-            removeDetachedView(mEditText, false /* animate */);
-        }
-        super.dispatchFinishTemporaryDetach();
-    }
-
-    /**
-     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
-     * whenever the user navigates away from it or it becomes invisible.
-     */
-    public static class RemoteEditText extends EditText {
-
-        private final Drawable mBackground;
-        private RemoteInputView mRemoteInputView;
-        boolean mShowImeOnInputConnection;
-
-        public RemoteEditText(Context context, AttributeSet attrs) {
-            super(context, attrs);
-            mBackground = getBackground();
-        }
-
-        private void defocusIfNeeded(boolean animate) {
-            if (mRemoteInputView != null || isTemporarilyDetached()) {
-                if (isTemporarilyDetached()) {
-                    // We might get reattached but then the other one of HUN / expanded might steal
-                    // our focus, so we'll need to save our text here.
-                }
-                return;
-            }
-            if (isFocusable() && isEnabled()) {
-                setInnerFocusable(false);
-                if (mRemoteInputView != null) {
-                    mRemoteInputView.onDefocus();
-                }
-                mShowImeOnInputConnection = false;
-            }
-        }
-
-        @Override
-        protected void onVisibilityChanged(View changedView, int visibility) {
-            super.onVisibilityChanged(changedView, visibility);
-
-            if (!isShown()) {
-                defocusIfNeeded(false /* animate */);
-            }
-        }
-
-        @Override
-        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
-            super.onFocusChanged(focused, direction, previouslyFocusedRect);
-            if (!focused) {
-                defocusIfNeeded(true /* animate */);
-            }
-        }
-
-        @Override
-        public void getFocusedRect(Rect r) {
-            super.getFocusedRect(r);
-            r.top = mScrollY;
-            r.bottom = mScrollY + (mBottom - mTop);
-        }
-
-        @Override
-        public boolean onKeyDown(int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
-                // Eat the DOWN event here to prevent any default behavior.
-                return true;
-            }
-            return super.onKeyDown(keyCode, event);
-        }
-
-        @Override
-        public boolean onKeyUp(int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
-                defocusIfNeeded(true /* animate */);
-                return true;
-            }
-            return super.onKeyUp(keyCode, event);
-        }
-
-        @Override
-        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
-
-            if (mShowImeOnInputConnection && inputConnection != null) {
-                final InputMethodManager imm = InputMethodManager.getInstance();
-                if (imm != null) {
-                    // onCreateInputConnection is called by InputMethodManager in the middle of
-                    // setting up the connection to the IME; wait with requesting the IME until that
-                    // work has completed.
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            imm.viewClicked(RemoteEditText.this);
-                            imm.showSoftInput(RemoteEditText.this, 0);
-                        }
-                    });
-                }
-            }
-
-            return inputConnection;
-        }
-
-        @Override
-        public void onCommitCompletion(CompletionInfo text) {
-            clearComposingText();
-            setText(text.getText());
-            setSelection(getText().length());
-        }
-
-        void setInnerFocusable(boolean focusable) {
-            setFocusableInTouchMode(focusable);
-            setFocusable(focusable);
-            setCursorVisible(focusable);
-
-            if (focusable) {
-                requestFocus();
-                setBackground(mBackground);
-            } else {
-                setBackground(null);
-            }
-
-        }
-    }
-}
diff --git a/core/java/android/app/slice/widget/ShortcutView.java b/core/java/android/app/slice/widget/ShortcutView.java
deleted file mode 100644
index 0b7ad0d..0000000
--- a/core/java/android/app/slice/widget/ShortcutView.java
+++ /dev/null
@@ -1,175 +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.
- */
-
-package android.app.slice.widget;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
-import android.net.Uri;
-
-import com.android.internal.R;
-
-import java.util.List;
-
-/**
- * @hide
- */
-public class ShortcutView extends SliceModeView {
-
-    private static final String TAG = "ShortcutView";
-
-    private Uri mUri;
-    private PendingIntent mAction;
-    private SliceItem mLabel;
-    private SliceItem mIcon;
-
-    private int mLargeIconSize;
-    private int mSmallIconSize;
-
-    public ShortcutView(Context context) {
-        super(context);
-        final Resources res = getResources();
-        mSmallIconSize = res.getDimensionPixelSize(R.dimen.slice_icon_size);
-        mLargeIconSize = res.getDimensionPixelSize(R.dimen.slice_shortcut_size);
-    }
-
-    @Override
-    public void setSlice(Slice slice) {
-        removeAllViews();
-        determineShortcutItems(getContext(), slice);
-        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
-        if (colorItem == null) {
-            colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
-        }
-        // TODO: pick better default colour
-        final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
-        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
-        circle.setTint(color);
-        setBackground(circle);
-        if (mIcon != null) {
-            final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE);
-            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
-            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
-                    isLarge, this /* parent */);
-            mUri = slice.getUri();
-            setClickable(true);
-        } else {
-            setClickable(false);
-        }
-    }
-
-    @Override
-    public String getMode() {
-        return SliceView.MODE_SHORTCUT;
-    }
-
-    @Override
-    public boolean performClick() {
-        if (!callOnClick()) {
-            try {
-                if (mAction != null) {
-                    mAction.send();
-                } else {
-                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
-                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    getContext().startActivity(intent);
-                }
-            } catch (CanceledException e) {
-                e.printStackTrace();
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Looks at the slice and determines which items are best to use to compose the shortcut.
-     */
-    private void determineShortcutItems(Context context, Slice slice) {
-        List<String> h = slice.getHints();
-        SliceItem sliceItem = new SliceItem(slice, SliceItem.TYPE_SLICE,
-                h.toArray(new String[h.size()]));
-        SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
-                Slice.HINT_TITLE, null);
-
-        if (titleItem != null) {
-            // Preferred case: hinted action containing hinted image and text
-            mAction = titleItem.getAction();
-            mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
-                    null);
-            mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
-                    null);
-        } else {
-            // No hinted action; just use the first one
-            SliceItem actionItem = SliceQuery.find(sliceItem, SliceItem.TYPE_ACTION, (String) null,
-                    null);
-            mAction = (actionItem != null) ? actionItem.getAction() : null;
-        }
-        // First fallback: any hinted image and text
-        if (mIcon == null) {
-            mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
-                    null);
-        }
-        if (mLabel == null) {
-            mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
-                    null);
-        }
-        // Second fallback: first image and text
-        if (mIcon == null) {
-            mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, (String) null,
-                    null);
-        }
-        if (mLabel == null) {
-            mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, (String) null,
-                    null);
-        }
-        // Final fallback: use app info
-        if (mIcon == null || mLabel == null || mAction == null) {
-            PackageManager pm = context.getPackageManager();
-            ProviderInfo providerInfo = pm.resolveContentProvider(
-                    slice.getUri().getAuthority(), 0);
-            ApplicationInfo appInfo = providerInfo.applicationInfo;
-            if (appInfo != null) {
-                if (mIcon == null) {
-                    Drawable icon = appInfo.loadDefaultIcon(pm);
-                    mIcon = new SliceItem(SliceViewUtil.createIconFromDrawable(icon),
-                            SliceItem.TYPE_IMAGE, new String[] {Slice.HINT_LARGE});
-                }
-                if (mLabel == null) {
-                    mLabel = new SliceItem(pm.getApplicationLabel(appInfo),
-                            SliceItem.TYPE_TEXT, null);
-                }
-                if (mAction == null) {
-                    mAction = PendingIntent.getActivity(context, 0,
-                            pm.getLaunchIntentForPackage(appInfo.packageName), 0);
-                }
-            }
-        }
-    }
-}
diff --git a/core/java/android/app/slice/widget/SliceView.java b/core/java/android/app/slice/widget/SliceView.java
deleted file mode 100644
index fa1b64c..0000000
--- a/core/java/android/app/slice/widget/SliceView.java
+++ /dev/null
@@ -1,422 +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.
- */
-
-package android.app.slice.widget;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-
-/**
- * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
- * able to present slice content in a templated format outside of the associated app. The way this
- * content is displayed depends on the structure of the slice, the hints associated with the
- * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
- * <ul>
- * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
- * content or action associated with the slice.</li>
- * <li><b>Small</b>: The small format has a restricted height and can present a single
- * {@link SliceItem} or a limited collection of items.</li>
- * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
- * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
- * comfortably fit.</li>
- * </ul>
- * <p>
- * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
- * with some information on how the content should be displayed. For example, text annotated with
- * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
- * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
- * <p>
- * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
- * observer will be set for that uri and the view will update if there are any changes to the slice.
- * To use this the app must have a special permission to bind to the slice (see
- * {@link android.Manifest.permission#BIND_SLICE}).
- * <p>
- * Example usage:
- *
- * <pre class="prettyprint">
- * SliceView v = new SliceView(getContext());
- * v.setMode(desiredMode);
- * v.setSlice(sliceUri);
- * </pre>
- */
-public class SliceView extends ViewGroup {
-
-    private static final String TAG = "SliceView";
-
-    /**
-     * @hide
-     */
-    public abstract static class SliceModeView extends FrameLayout {
-
-        public SliceModeView(Context context) {
-            super(context);
-        }
-
-        /**
-         * @return the mode of the slice being presented.
-         */
-        public abstract String getMode();
-
-        /**
-         * @param slice the slice to show in this view.
-         */
-        public abstract void setSlice(Slice slice);
-    }
-
-    /**
-     * @hide
-     */
-    @StringDef({
-            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
-    })
-    public @interface SliceMode {}
-
-    /**
-     * Mode indicating this slice should be presented in small template format.
-     */
-    public static final String MODE_SMALL       = "SLICE_SMALL";
-    /**
-     * Mode indicating this slice should be presented in large template format.
-     */
-    public static final String MODE_LARGE       = "SLICE_LARGE";
-    /**
-     * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
-     * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
-     * slice.
-     */
-    public static final String MODE_SHORTCUT    = "SLICE_ICON";
-
-    /**
-     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
-     * that selection.
-     */
-    private static final String MODE_AUTO = "auto";
-
-    private String mMode = MODE_AUTO;
-    private SliceModeView mCurrentView;
-    private final ActionRow mActions;
-    private Slice mCurrentSlice;
-    private boolean mShowActions = true;
-    private boolean mIsScrollable;
-    private SliceObserver mObserver;
-    private final int mShortcutSize;
-
-    public SliceView(Context context) {
-        this(context, null);
-    }
-
-    public SliceView(Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
-        mActions = new ActionRow(mContext, true);
-        mActions.setBackground(new ColorDrawable(0xffeeeeee));
-        mCurrentView = new LargeTemplateView(mContext);
-        addView(mCurrentView, getChildLp(mCurrentView));
-        addView(mActions, getChildLp(mActions));
-        mShortcutSize = getContext().getResources()
-                .getDimensionPixelSize(R.dimen.slice_shortcut_size);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        measureChildren(widthMeasureSpec, heightMeasureSpec);
-        int actionHeight = mActions.getVisibility() != View.GONE
-                ? mActions.getMeasuredHeight()
-                : 0;
-        int newHeightSpec = MeasureSpec.makeMeasureSpec(
-                mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        setMeasuredDimension(width, newHeightSpec);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
-                t + mCurrentView.getMeasuredHeight());
-        if (mActions.getVisibility() != View.GONE) {
-            mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
-                    mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
-        }
-    }
-
-    /**
-     * Populates this view with the {@link Slice} associated with the provided {@link Intent}. To
-     * use this method your app must have the permission
-     * {@link android.Manifest.permission#BIND_SLICE}).
-     * <p>
-     * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
-     * updated with the slice identified by the provided intent changes. The lifecycle of this
-     * observer is handled by SliceView in {@link #onAttachedToWindow()} and
-     * {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call
-     * {@link #clearSlice}.
-     *
-     * @return true if a slice was found for the provided intent.
-     * @hide
-     */
-    public boolean setSlice(@Nullable Intent intent) {
-        Slice s = Slice.bindSlice(mContext, intent);
-        if (s != null) {
-            return setSlice(s.getUri());
-        }
-        return s != null;
-    }
-
-    /**
-     * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use
-     * this method your app must have the permission
-     * {@link android.Manifest.permission#BIND_SLICE}).
-     * <p>
-     * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
-     * updated when the slice identified by the provided URI changes. The lifecycle of this observer
-     * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
-     * To unregister this observer outside of that you can call {@link #clearSlice}.
-     *
-     * @return true if a slice was found for the provided uri.
-     */
-    public boolean setSlice(@NonNull Uri sliceUri) {
-        Preconditions.checkNotNull(sliceUri,
-                "Uri cannot be null, to remove the slice use clearSlice()");
-        if (sliceUri == null) {
-            clearSlice();
-            return false;
-        }
-        validate(sliceUri);
-        Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
-        if (s != null) {
-            if (mObserver != null) {
-                getContext().getContentResolver().unregisterContentObserver(mObserver);
-            }
-            mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
-            if (isAttachedToWindow()) {
-                registerSlice(sliceUri);
-            }
-            mCurrentSlice = s;
-            reinflate();
-        }
-        return s != null;
-    }
-
-    /**
-     * Populates this view to the provided {@link Slice}.
-     * <p>
-     * This does not register a content observer on the URI that the slice is backed by so it will
-     * not update if the content changes. To have the view update when the content changes use
-     * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
-     * any special permissions.
-     */
-    public void showSlice(@NonNull Slice slice) {
-        Preconditions.checkNotNull(slice,
-                "Slice cannot be null, to remove the slice use clearSlice()");
-        clearSlice();
-        mCurrentSlice = slice;
-        reinflate();
-    }
-
-    /**
-     * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
-     * done automatically during {@link #onDetachedFromWindow()}.
-     * <p>
-     * It is safe to call this method multiple times.
-     */
-    public void clearSlice() {
-        mCurrentSlice = null;
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-            mObserver = null;
-        }
-    }
-
-    /**
-     * Set the mode this view should present in.
-     */
-    public void setMode(@SliceMode String mode) {
-        setMode(mode, false /* animate */);
-    }
-
-    /**
-     * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
-     */
-    public void setScrollable(boolean isScrollable) {
-        mIsScrollable = isScrollable;
-        reinflate();
-    }
-
-    /**
-     * @hide
-     */
-    public void setMode(@SliceMode String mode, boolean animate) {
-        if (animate) {
-            Log.e(TAG, "Animation not supported yet");
-        }
-        mMode = mode;
-        reinflate();
-    }
-
-    /**
-     * @return the mode this view is presenting in.
-     */
-    public @SliceMode String getMode() {
-        if (mMode.equals(MODE_AUTO)) {
-            return MODE_LARGE;
-        }
-        return mMode;
-    }
-
-    /**
-     * @hide
-     *
-     * Whether this view should show a row of actions with it.
-     */
-    public void setShowActionRow(boolean show) {
-        mShowActions = show;
-        reinflate();
-    }
-
-    private SliceModeView createView(String mode) {
-        switch (mode) {
-            case MODE_SHORTCUT:
-                return new ShortcutView(getContext());
-            case MODE_SMALL:
-                return new SmallTemplateView(getContext());
-        }
-        return new LargeTemplateView(getContext());
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-            mObserver = null;
-        }
-    }
-
-    private void registerSlice(Uri sliceUri) {
-        if (sliceUri == null || mObserver == null) {
-            return;
-        }
-        mContext.getContentResolver().registerContentObserver(sliceUri,
-                false /* notifyForDescendants */, mObserver);
-    }
-
-    private void reinflate() {
-        if (mCurrentSlice == null) {
-            return;
-        }
-        // TODO: Smarter mapping here from one state to the next.
-        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
-        List<SliceItem> items = mCurrentSlice.getItems();
-        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
-                Slice.HINT_ACTIONS,
-                Slice.HINT_ALT);
-        String mode = getMode();
-        if (!mode.equals(mCurrentView.getMode())) {
-            removeAllViews();
-            mCurrentView = createView(mode);
-            addView(mCurrentView, getChildLp(mCurrentView));
-            addView(mActions, getChildLp(mActions));
-        }
-        if (mode.equals(MODE_LARGE)) {
-            ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
-        }
-        if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
-            mCurrentView.setVisibility(View.VISIBLE);
-            mCurrentView.setSlice(mCurrentSlice);
-        } else {
-            mCurrentView.setVisibility(View.GONE);
-        }
-
-        boolean showActions = mShowActions && actionRow != null
-                && !mode.equals(MODE_SHORTCUT);
-        if (showActions) {
-            mActions.setActions(actionRow, color);
-            mActions.setVisibility(View.VISIBLE);
-        } else {
-            mActions.setVisibility(View.GONE);
-        }
-    }
-
-    private LayoutParams getChildLp(View child) {
-        if (child instanceof ShortcutView) {
-            return new LayoutParams(mShortcutSize, mShortcutSize);
-        } else {
-            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
-        }
-    }
-
-    private static void validate(Uri sliceUri) {
-        if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
-            throw new RuntimeException("Invalid uri " + sliceUri);
-        }
-        if (sliceUri.getPathSegments().size() == 0) {
-            throw new RuntimeException("Invalid uri " + sliceUri);
-        }
-    }
-
-    private class SliceObserver extends ContentObserver {
-        SliceObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            this.onChange(selfChange, null);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
-            mCurrentSlice = s;
-            reinflate();
-        }
-    }
-}
diff --git a/core/java/android/app/slice/widget/SliceViewUtil.java b/core/java/android/app/slice/widget/SliceViewUtil.java
deleted file mode 100644
index 1cf0055..0000000
--- a/core/java/android/app/slice/widget/SliceViewUtil.java
+++ /dev/null
@@ -1,198 +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.
- */
-
-package android.app.slice.widget;
-
-import android.annotation.ColorInt;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-/**
- * A bunch of utilities for slice UI.
- *
- * @hide
- */
-public class SliceViewUtil {
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int getColorAccent(Context context) {
-        return getColorAttr(context, android.R.attr.colorAccent);
-    }
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int getColorError(Context context) {
-        return getColorAttr(context, android.R.attr.colorError);
-    }
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int getDefaultColor(Context context, int resId) {
-        final ColorStateList list = context.getResources().getColorStateList(resId,
-                context.getTheme());
-
-        return list.getDefaultColor();
-    }
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int getDisabled(Context context, int inputColor) {
-        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
-    }
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int applyAlphaAttr(Context context, int attr, int inputColor) {
-        TypedArray ta = context.obtainStyledAttributes(new int[] {
-                attr
-        });
-        float alpha = ta.getFloat(0, 0);
-        ta.recycle();
-        return applyAlpha(alpha, inputColor);
-    }
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int applyAlpha(float alpha, int inputColor) {
-        alpha *= Color.alpha(inputColor);
-        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
-                Color.blue(inputColor));
-    }
-
-    /**
-     * @hide
-     */
-    @ColorInt
-    public static int getColorAttr(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[] {
-                attr
-        });
-        @ColorInt
-        int colorAccent = ta.getColor(0, 0);
-        ta.recycle();
-        return colorAccent;
-    }
-
-    /**
-     * @hide
-     */
-    public static int getThemeAttr(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[] {
-                attr
-        });
-        int theme = ta.getResourceId(0, 0);
-        ta.recycle();
-        return theme;
-    }
-
-    /**
-     * @hide
-     */
-    public static Drawable getDrawable(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[] {
-                attr
-        });
-        Drawable drawable = ta.getDrawable(0);
-        ta.recycle();
-        return drawable;
-    }
-
-    /**
-     * @hide
-     */
-    public static Icon createIconFromDrawable(Drawable d) {
-        if (d instanceof BitmapDrawable) {
-            return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
-        }
-        Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
-                Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(b);
-        d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
-        d.draw(canvas);
-        return Icon.createWithBitmap(b);
-    }
-
-    /**
-     * @hide
-     */
-    public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
-            boolean isLarge, ViewGroup parent) {
-        ImageView v = new ImageView(context);
-        v.setImageIcon(icon);
-        parent.addView(v);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
-        if (isLarge) {
-            // XXX better way to convert from icon -> bitmap or crop an icon (?)
-            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
-            Canvas iconCanvas = new Canvas(iconBm);
-            v.layout(0, 0, iconSize, iconSize);
-            v.draw(iconCanvas);
-            v.setImageBitmap(getCircularBitmap(iconBm));
-        } else {
-            v.setColorFilter(Color.WHITE);
-        }
-        lp.width = iconSize;
-        lp.height = iconSize;
-        lp.gravity = Gravity.CENTER;
-    }
-
-    /**
-     * @hide
-     */
-    public static Bitmap getCircularBitmap(Bitmap bitmap) {
-        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
-                bitmap.getHeight(), Config.ARGB_8888);
-        Canvas canvas = new Canvas(output);
-        final Paint paint = new Paint();
-        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
-        paint.setAntiAlias(true);
-        canvas.drawARGB(0, 0, 0, 0);
-        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
-                bitmap.getWidth() / 2, paint);
-        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
-        canvas.drawBitmap(bitmap, rect, rect, paint);
-        return output;
-    }
-}
diff --git a/core/java/android/app/slice/widget/SmallTemplateView.java b/core/java/android/app/slice/widget/SmallTemplateView.java
deleted file mode 100644
index 1c4c5df..0000000
--- a/core/java/android/app/slice/widget/SmallTemplateView.java
+++ /dev/null
@@ -1,211 +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.
- */
-
-package android.app.slice.widget;
-
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.app.slice.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.text.Format;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Small template is also used to construct list items for use with {@link LargeTemplateView}.
- *
- * @hide
- */
-public class SmallTemplateView extends SliceModeView implements SliceListView {
-
-    private static final String TAG = "SmallTemplateView";
-
-    private int mIconSize;
-    private int mPadding;
-
-    private LinearLayout mStartContainer;
-    private TextView mTitleText;
-    private TextView mSecondaryText;
-    private LinearLayout mEndContainer;
-
-    public SmallTemplateView(Context context) {
-        super(context);
-        inflate(context, R.layout.slice_small_template, this);
-        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
-        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
-
-        mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
-        mTitleText = (TextView) findViewById(android.R.id.title);
-        mSecondaryText = (TextView) findViewById(android.R.id.summary);
-        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
-    }
-
-    @Override
-    public String getMode() {
-        return SliceView.MODE_SMALL;
-    }
-
-    @Override
-    public void setSliceItem(SliceItem slice) {
-        resetViews();
-        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
-        int color = colorItem != null ? colorItem.getColor() : -1;
-
-        // Look for any title elements
-        List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
-                null);
-        boolean hasTitleText = false;
-        boolean hasTitleItem = false;
-        for (int i = 0; i < titleItems.size(); i++) {
-            SliceItem item = titleItems.get(i);
-            if (!hasTitleItem) {
-                // icon, action icon, or timestamp
-                if (item.getType() == SliceItem.TYPE_ACTION) {
-                    hasTitleItem = addIcon(item, color, mStartContainer);
-                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
-                    addIcon(item, color, mStartContainer);
-                    hasTitleItem = true;
-                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
-                    TextView tv = new TextView(getContext());
-                    tv.setText(convertTimeToString(item.getTimestamp()));
-                    hasTitleItem = true;
-                }
-            }
-            if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
-                mTitleText.setText(item.getText());
-                hasTitleText = true;
-            }
-            if (hasTitleText && hasTitleItem) {
-                break;
-            }
-        }
-        mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
-        mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
-
-        if (slice.getType() != SliceItem.TYPE_SLICE) {
-            return;
-        }
-
-        // Deal with remaining items
-        int itemCount = 0;
-        boolean hasSummary = false;
-        ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
-                slice.getSlice().getItems());
-        for (int i = 0; i < sliceItems.size(); i++) {
-            SliceItem item = sliceItems.get(i);
-            if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
-                    && !item.hasHint(Slice.HINT_TITLE)) {
-                // TODO -- Should combine all text items?
-                mSecondaryText.setText(item.getText());
-                hasSummary = true;
-            }
-            if (itemCount <= 3) {
-                if (item.getType() == SliceItem.TYPE_ACTION) {
-                    if (addIcon(item, color, mEndContainer)) {
-                        itemCount++;
-                    }
-                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
-                    addIcon(item, color, mEndContainer);
-                    itemCount++;
-                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
-                    TextView tv = new TextView(getContext());
-                    tv.setText(convertTimeToString(item.getTimestamp()));
-                    mEndContainer.addView(tv);
-                    itemCount++;
-                } else if (item.getType() == SliceItem.TYPE_SLICE) {
-                    List<SliceItem> subItems = item.getSlice().getItems();
-                    for (int j = 0; j < subItems.size(); j++) {
-                        sliceItems.add(subItems.get(j));
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void setSlice(Slice slice) {
-        setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
-                slice.getHints().toArray(new String[slice.getHints().size()])));
-    }
-
-    /**
-     * @return Whether an icon was added.
-     */
-    private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
-        SliceItem image = null;
-        SliceItem action = null;
-        if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
-            image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
-            action = sliceItem;
-        } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
-            image = sliceItem;
-        }
-        if (image != null) {
-            ImageView iv = new ImageView(getContext());
-            iv.setImageIcon(image.getIcon());
-            if (action != null) {
-                final SliceItem sliceAction = action;
-                iv.setOnClickListener(v -> AsyncTask.execute(
-                        () -> {
-                            try {
-                                sliceAction.getAction().send();
-                            } catch (CanceledException e) {
-                                e.printStackTrace();
-                            }
-                        }));
-                iv.setBackground(SliceViewUtil.getDrawable(getContext(),
-                        android.R.attr.selectableItemBackground));
-            }
-            if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
-                iv.setColorFilter(color);
-            }
-            container.addView(iv);
-            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
-            lp.width = mIconSize;
-            lp.height = mIconSize;
-            lp.setMarginStart(mPadding);
-            return true;
-        }
-        return false;
-    }
-
-    private String convertTimeToString(long time) {
-        // TODO -- figure out what format(s) we support
-        Date date = new Date(time);
-        Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
-        return format.format(date);
-    }
-
-    private void resetViews() {
-        mStartContainer.removeAllViews();
-        mEndContainer.removeAllViews();
-        mTitleText.setText(null);
-        mSecondaryText.setText(null);
-    }
-}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 0d7a941..8200414 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -100,6 +100,12 @@
          */
         public static final int CHOOSER_ACTION = 9;
 
+        /**
+         * An event type denoting that a notification was viewed by the user.
+         * @hide
+         */
+        public static final int NOTIFICATION_SEEN = 10;
+
         /** @hide */
         public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
 
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 33386e5..7f24c51 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -145,7 +145,7 @@
     }
 
     /**
-     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * Starts an asynchronous load of the data. When the result is ready the callbacks
      * will be called on the UI thread. If a previous load has been completed and is still valid
      * the result may be passed to the callbacks immediately.
      *
diff --git a/core/java/android/content/pm/IPackageInstallObserver.aidl b/core/java/android/content/pm/IPackageInstallObserver.aidl
deleted file mode 100644
index 6133365..0000000
--- a/core/java/android/content/pm/IPackageInstallObserver.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-**
-** Copyright 2007, 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.content.pm;
-
-/**
- * API for installation callbacks from the Package Manager.
- * @hide
- */
-oneway interface IPackageInstallObserver {
-    void packageInstalled(in String packageName, int returnCode);
-}
-
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1352bc2..64d33d5 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -543,9 +543,10 @@
     void forceDexOpt(String packageName);
 
     /**
-     * Execute the background dexopt job immediately.
+     * Execute the background dexopt job immediately on packages in packageNames.
+     * If null, then execute on all packages.
      */
-    boolean runBackgroundDexoptJob();
+    boolean runBackgroundDexoptJob(in List<String> packageNames);
 
     /**
      * Reconcile the information we have about the secondary dex files belonging to
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 86288396..77c5743 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -81,6 +81,9 @@
  * <li>All APKs must have unique split names.
  * <li>All installations must contain a single base APK.
  * </ul>
+ * <p>
+ * The ApiDemos project contains examples of using this API:
+ * <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>.
  */
 public class PackageInstaller {
     private static final String TAG = "PackageInstaller";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 31ca198..15ea4b5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -871,8 +871,8 @@
     public static final int INSTALL_REASON_USER = 4;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} on success.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * on success.
      *
      * @hide
      */
@@ -880,8 +880,8 @@
     public static final int INSTALL_SUCCEEDED = 1;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the package is already installed.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the package is already installed.
      *
      * @hide
      */
@@ -889,8 +889,8 @@
     public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the package archive file is invalid.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the package archive file is invalid.
      *
      * @hide
      */
@@ -898,8 +898,8 @@
     public static final int INSTALL_FAILED_INVALID_APK = -2;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the URI passed in is invalid.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the URI passed in is invalid.
      *
      * @hide
      */
@@ -907,9 +907,9 @@
     public static final int INSTALL_FAILED_INVALID_URI = -3;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the package manager service found that
-     * the device didn't have enough storage space to install the app.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the package manager service found that the device didn't have enough storage space to
+     * install the app.
      *
      * @hide
      */
@@ -917,9 +917,8 @@
     public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if a package is already installed with
-     * the same name.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if a package is already installed with the same name.
      *
      * @hide
      */
@@ -927,9 +926,8 @@
     public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the requested shared user does not
-     * exist.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the requested shared user does not exist.
      *
      * @hide
      */
@@ -937,10 +935,9 @@
     public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if a previously installed package of the
-     * same name has a different signature than the new package (and the old
-     * package's data was not removed).
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if a previously installed package of the same name has a different signature than the new
+     * package (and the old package's data was not removed).
      *
      * @hide
      */
@@ -948,10 +945,9 @@
     public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package is requested a shared
-     * user which is already installed on the device and does not have matching
-     * signature.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package is requested a shared user which is already installed on the device and
+     * does not have matching signature.
      *
      * @hide
      */
@@ -959,9 +955,8 @@
     public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package uses a shared library
-     * that is not available.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package uses a shared library that is not available.
      *
      * @hide
      */
@@ -969,9 +964,8 @@
     public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package uses a shared library
-     * that is not available.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package uses a shared library that is not available.
      *
      * @hide
      */
@@ -979,10 +973,9 @@
     public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package failed while
-     * optimizing and validating its dex files, either because there was not
-     * enough storage or the validation failed.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package failed while optimizing and validating its dex files, either because there
+     * was not enough storage or the validation failed.
      *
      * @hide
      */
@@ -990,9 +983,9 @@
     public static final int INSTALL_FAILED_DEXOPT = -11;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package failed because the
-     * current SDK version is older than that required by the package.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package failed because the current SDK version is older than that required by the
+     * package.
      *
      * @hide
      */
@@ -1000,10 +993,9 @@
     public static final int INSTALL_FAILED_OLDER_SDK = -12;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package failed because it
-     * contains a content provider with the same authority as a provider already
-     * installed in the system.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package failed because it contains a content provider with the same authority as a
+     * provider already installed in the system.
      *
      * @hide
      */
@@ -1011,9 +1003,9 @@
     public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package failed because the
-     * current SDK version is newer than that required by the package.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package failed because the current SDK version is newer than that required by the
+     * package.
      *
      * @hide
      */
@@ -1021,10 +1013,9 @@
     public static final int INSTALL_FAILED_NEWER_SDK = -14;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package failed because it has
-     * specified that it is a test-only package and the caller has not supplied
-     * the {@link #INSTALL_ALLOW_TEST} flag.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package failed because it has specified that it is a test-only package and the
+     * caller has not supplied the {@link #INSTALL_ALLOW_TEST} flag.
      *
      * @hide
      */
@@ -1032,9 +1023,9 @@
     public static final int INSTALL_FAILED_TEST_ONLY = -15;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the package being installed contains
-     * native code, but none that is compatible with the device's CPU_ABI.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the package being installed contains native code, but none that is compatible with the
+     * device's CPU_ABI.
      *
      * @hide
      */
@@ -1042,9 +1033,8 @@
     public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package uses a feature that is
-     * not available.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package uses a feature that is not available.
      *
      * @hide
      */
@@ -1053,9 +1043,9 @@
 
     // ------ Errors related to sdcard
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if a secure container mount point
-     * couldn't be accessed on external media.
+     * Installation return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if a secure container mount point couldn't be
+     * accessed on external media.
      *
      * @hide
      */
@@ -1063,9 +1053,8 @@
     public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package couldn't be installed
-     * in the specified install location.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package couldn't be installed in the specified install location.
      *
      * @hide
      */
@@ -1073,9 +1062,9 @@
     public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package couldn't be installed
-     * in the specified install location because the media is not available.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package couldn't be installed in the specified install location because the media
+     * is not available.
      *
      * @hide
      */
@@ -1083,9 +1072,8 @@
     public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package couldn't be installed
-     * because the verification timed out.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package couldn't be installed because the verification timed out.
      *
      * @hide
      */
@@ -1093,9 +1081,8 @@
     public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package couldn't be installed
-     * because the verification did not succeed.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package couldn't be installed because the verification did not succeed.
      *
      * @hide
      */
@@ -1103,9 +1090,8 @@
     public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the package changed from what the
-     * calling program expected.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the package changed from what the calling program expected.
      *
      * @hide
      */
@@ -1113,28 +1099,25 @@
     public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package is assigned a
-     * different UID than it previously held.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package is assigned a different UID than it previously held.
      *
      * @hide
      */
     public static final int INSTALL_FAILED_UID_CHANGED = -24;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package has an older version
-     * code than the currently installed package.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package has an older version code than the currently installed package.
      *
      * @hide
      */
     public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the old package has target SDK high
-     * enough to support runtime permission and the new package has target SDK
-     * low enough to not support runtime permissions.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the old package has target SDK high enough to support runtime permission and the new
+     * package has target SDK low enough to not support runtime permissions.
      *
      * @hide
      */
@@ -1142,9 +1125,8 @@
     public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26;
 
     /**
-     * Installation return code: this is passed to the
-     * {@link IPackageInstallObserver} if the new package attempts to downgrade the
-     * target sandbox version of the app.
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package attempts to downgrade the target sandbox version of the app.
      *
      * @hide
      */
@@ -1152,9 +1134,9 @@
     public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser was given a path that is
-     * not a file, or does not end with the expected '.apk' extension.
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a
+     * file, or does not end with the expected '.apk' extension.
      *
      * @hide
      */
@@ -1162,8 +1144,8 @@
     public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser was unable to retrieve the
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was unable to retrieve the
      * AndroidManifest.xml file.
      *
      * @hide
@@ -1172,8 +1154,8 @@
     public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser encountered an unexpected
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered an unexpected
      * exception.
      *
      * @hide
@@ -1182,9 +1164,9 @@
     public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser did not find any
-     * certificates in the .apk.
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any certificates in
+     * the .apk.
      *
      * @hide
      */
@@ -1192,9 +1174,9 @@
     public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser found inconsistent
-     * certificates on the files in the .apk.
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser found inconsistent certificates on
+     * the files in the .apk.
      *
      * @hide
      */
@@ -1202,8 +1184,8 @@
     public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser encountered a
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a
      * CertificateEncodingException in one of the files in the .apk.
      *
      * @hide
@@ -1212,9 +1194,9 @@
     public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser encountered a bad or
-     * missing package name in the manifest.
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad or missing
+     * package name in the manifest.
      *
      * @hide
      */
@@ -1222,9 +1204,9 @@
     public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser encountered a bad shared
-     * user id name in the manifest.
+     * Installation parse return code: tthis is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad shared user id
+     * name in the manifest.
      *
      * @hide
      */
@@ -1232,8 +1214,8 @@
     public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser encountered some structural
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered some structural
      * problem in the manifest.
      *
      * @hide
@@ -1242,9 +1224,9 @@
     public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
 
     /**
-     * Installation parse return code: this is passed to the
-     * {@link IPackageInstallObserver} if the parser did not find any actionable
-     * tags (instrumentation or application) in the manifest.
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any actionable tags
+     * (instrumentation or application) in the manifest.
      *
      * @hide
      */
@@ -1252,9 +1234,9 @@
     public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
 
     /**
-     * Installation failed return code: this is passed to the
-     * {@link IPackageInstallObserver} if the system failed to install the
-     * package because of system issues.
+     * Installation failed return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+     * because of system issues.
      *
      * @hide
      */
@@ -1262,24 +1244,23 @@
     public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
 
     /**
-     * Installation failed return code: this is passed to the
-     * {@link IPackageInstallObserver} if the system failed to install the
-     * package because the user is restricted from installing apps.
+     * Installation failed return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+     * because the user is restricted from installing apps.
      *
      * @hide
      */
     public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
 
     /**
-     * Installation failed return code: this is passed to the
-     * {@link IPackageInstallObserver} if the system failed to install the
-     * package because it is attempting to define a permission that is already
-     * defined by some existing package.
+     * Installation failed return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+     * because it is attempting to define a permission that is already defined by some existing
+     * package.
      * <p>
-     * The package name of the app which has already defined the permission is
-     * passed to a {@link PackageInstallObserver}, if any, as the
-     * {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string extra; and the name of the
-     * permission being redefined is passed in the
+     * The package name of the app which has already defined the permission is passed to a
+     * {@link PackageInstallObserver}, if any, as the {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string
+     * extra; and the name of the permission being redefined is passed in the
      * {@link #EXTRA_FAILURE_EXISTING_PERMISSION} string extra.
      *
      * @hide
@@ -1287,10 +1268,9 @@
     public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112;
 
     /**
-     * Installation failed return code: this is passed to the
-     * {@link IPackageInstallObserver} if the system failed to install the
-     * package because its packaged native code did not match any of the ABIs
-     * supported by the system.
+     * Installation failed return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+     * because its packaged native code did not match any of the ABIs supported by the system.
      *
      * @hide
      */
@@ -4718,16 +4698,6 @@
     @Deprecated
     public abstract void installPackage(
             Uri packageURI,
-            IPackageInstallObserver observer,
-            @InstallFlags int flags,
-            String installerPackageName);
-    /**
-     * @deprecated replaced by {@link PackageInstaller}
-     * @hide
-     */
-    @Deprecated
-    public abstract void installPackage(
-            Uri packageURI,
             PackageInstallObserver observer,
             @InstallFlags int flags,
             String installerPackageName);
@@ -5743,25 +5713,6 @@
     }
 
     /** {@hide} */
-    public static class LegacyPackageInstallObserver extends PackageInstallObserver {
-        private final IPackageInstallObserver mLegacy;
-
-        public LegacyPackageInstallObserver(IPackageInstallObserver legacy) {
-            mLegacy = legacy;
-        }
-
-        @Override
-        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
-                Bundle extras) {
-            if (mLegacy == null) return;
-            try {
-                mLegacy.packageInstalled(basePackageName, returnCode);
-            } catch (RemoteException ignored) {
-            }
-        }
-    }
-
-    /** {@hide} */
     public static class LegacyPackageDeleteObserver extends PackageDeleteObserver {
         private final IPackageDeleteObserver mLegacy;
 
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 8b0fef4..5adb119 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -570,6 +570,16 @@
         mAvailableNonPrimaryConnections.clear();
     }
 
+    /**
+     * Close non-primary connections that are not currently in use. This method is safe to use
+     * in finalize block as it doesn't throw RuntimeExceptions.
+     */
+    void closeAvailableNonPrimaryConnectionsAndLogExceptions() {
+        synchronized (mLock) {
+            closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
+        }
+    }
+
     // Can't throw.
     private void closeExcessConnectionsAndLogExceptionsLocked() {
         int availableCount = mAvailableNonPrimaryConnections.size();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 863fb19..09bb9c6 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1740,7 +1740,8 @@
     private int executeSql(String sql, Object[] bindArgs) throws SQLException {
         acquireReference();
         try {
-            if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+            final int statementType = DatabaseUtils.getSqlStatementType(sql);
+            if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
                 boolean disableWal = false;
                 synchronized (mLock) {
                     if (!mHasAttachedDbsLocked) {
@@ -1754,11 +1755,14 @@
                 }
             }
 
-            SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
-            try {
+            try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
                 return statement.executeUpdateDelete();
             } finally {
-                statement.close();
+                // If schema was updated, close non-primary connections, otherwise they might
+                // have outdated schema information
+                if (statementType == DatabaseUtils.STATEMENT_DDL) {
+                    mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+                }
             }
         } finally {
             releaseReference();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e845359..cd551bd 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -174,6 +174,11 @@
     public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
 
     /**
+     * Persist brightness slider events.
+     */
+    public abstract void persistBrightnessSliderEvents();
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index aaf6c57..e1137aa 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -16,6 +16,7 @@
 package android.hardware.location;
 
 import android.annotation.SystemApi;
+import android.hardware.contexthub.V1_0.ContextHub;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -31,22 +32,52 @@
     private String mVendor;
     private String mToolchain;
     private int mPlatformVersion;
-    private int mStaticSwVersion;
     private int mToolchainVersion;
     private float mPeakMips;
     private float mStoppedPowerDrawMw;
     private float mSleepPowerDrawMw;
     private float mPeakPowerDrawMw;
     private int mMaxPacketLengthBytes;
+    private byte mChreApiMajorVersion;
+    private byte mChreApiMinorVersion;
+    private short mChrePatchVersion;
+    private long mChrePlatformId;
 
     private int[] mSupportedSensors;
 
     private MemoryRegion[] mMemoryRegions;
 
+    /*
+     * TODO(b/67734082): Deprecate this constructor and mark private fields as final.
+     */
     public ContextHubInfo() {
     }
 
     /**
+     * @hide
+     */
+    public ContextHubInfo(ContextHub contextHub) {
+        mId = contextHub.hubId;
+        mName = contextHub.name;
+        mVendor = contextHub.vendor;
+        mToolchain = contextHub.toolchain;
+        mPlatformVersion = contextHub.platformVersion;
+        mToolchainVersion = contextHub.toolchainVersion;
+        mPeakMips = contextHub.peakMips;
+        mStoppedPowerDrawMw = contextHub.stoppedPowerDrawMw;
+        mSleepPowerDrawMw = contextHub.sleepPowerDrawMw;
+        mPeakPowerDrawMw = contextHub.peakPowerDrawMw;
+        mMaxPacketLengthBytes = contextHub.maxSupportedMsgLen;
+        mChrePlatformId = contextHub.chrePlatformId;
+        mChreApiMajorVersion = contextHub.chreApiMajorVersion;
+        mChreApiMinorVersion = contextHub.chreApiMinorVersion;
+        mChrePatchVersion = contextHub.chrePatchVersion;
+
+        mSupportedSensors = new int[0];
+        mMemoryRegions = new MemoryRegion[0];
+    }
+
+    /**
      * returns the maximum number of bytes that can be sent per message to the hub
      *
      * @return int - maximum bytes that can be transmitted in a
@@ -57,17 +88,6 @@
     }
 
     /**
-     * set the context hub unique identifer
-     *
-     * @param bytes - Maximum number of bytes per message
-     *
-     * @hide
-     */
-    public void setMaxPacketLenBytes(int bytes) {
-        mMaxPacketLengthBytes = bytes;
-    }
-
-    /**
      * get the context hub unique identifer
      *
      * @return int - unique system wide identifier
@@ -77,17 +97,6 @@
     }
 
     /**
-     * set the context hub unique identifer
-     *
-     * @param id - unique system wide identifier for the hub
-     *
-     * @hide
-     */
-    public void setId(int id) {
-        mId = id;
-    }
-
-    /**
      * get a string as a hub name
      *
      * @return String - a name for the hub
@@ -97,17 +106,6 @@
     }
 
     /**
-     * set a string as the hub name
-     *
-     * @param name - the name for the hub
-     *
-     * @hide
-     */
-    public void setName(String name) {
-        mName = name;
-    }
-
-    /**
      * get a string as the vendor name
      *
      * @return String - a name for the vendor
@@ -117,17 +115,6 @@
     }
 
     /**
-     * set a string as the vendor name
-     *
-     * @param vendor - a name for the vendor
-     *
-     * @hide
-     */
-    public void setVendor(String vendor) {
-        mVendor = vendor;
-    }
-
-    /**
      * get tool chain string
      *
      * @return String - description of the tool chain
@@ -137,17 +124,6 @@
     }
 
     /**
-     * set tool chain string
-     *
-     * @param toolchain - description of the tool chain
-     *
-     * @hide
-     */
-    public void setToolchain(String toolchain) {
-        mToolchain = toolchain;
-    }
-
-    /**
      * get platform version
      *
      * @return int - platform version number
@@ -157,34 +133,12 @@
     }
 
     /**
-     * set platform version
-     *
-     * @param platformVersion - platform version number
-     *
-     * @hide
-     */
-    public void setPlatformVersion(int platformVersion) {
-        mPlatformVersion = platformVersion;
-    }
-
-    /**
      * get static platform version number
      *
      * @return int - platform version number
      */
     public int getStaticSwVersion() {
-        return mStaticSwVersion;
-    }
-
-    /**
-     * set platform software version
-     *
-     * @param staticSwVersion - platform static s/w version number
-     *
-     * @hide
-     */
-    public void setStaticSwVersion(int staticSwVersion) {
-        mStaticSwVersion = staticSwVersion;
+        return (mChreApiMajorVersion << 24) | (mChreApiMinorVersion << 16) | (mChrePatchVersion);
     }
 
     /**
@@ -197,17 +151,6 @@
     }
 
     /**
-     * set the tool chain version number
-     *
-     * @param toolchainVersion - tool chain version number
-     *
-     * @hide
-     */
-    public void setToolchainVersion(int toolchainVersion) {
-        mToolchainVersion = toolchainVersion;
-    }
-
-    /**
      * get the peak processing mips the hub can support
      *
      * @return float - peak MIPS that this hub can deliver
@@ -217,17 +160,6 @@
     }
 
     /**
-     * set the peak mips that this hub can support
-     *
-     * @param peakMips - peak mips this hub can deliver
-     *
-     * @hide
-     */
-    public void setPeakMips(float peakMips) {
-        mPeakMips = peakMips;
-    }
-
-    /**
      * get the stopped power draw in milliwatts
      * This assumes that the hub enter a stopped state - which is
      * different from the sleep state. Latencies on exiting the
@@ -241,17 +173,6 @@
     }
 
     /**
-     * Set the power consumed by the hub in stopped state
-     *
-     * @param stoppedPowerDrawMw - stopped power in milli watts
-     *
-     * @hide
-     */
-    public void setStoppedPowerDrawMw(float stoppedPowerDrawMw) {
-        mStoppedPowerDrawMw = stoppedPowerDrawMw;
-    }
-
-    /**
      * get the power draw of the hub in sleep mode. This assumes
      * that the hub supports a sleep mode in which the power draw is
      * lower than the power consumed when the hub is actively
@@ -267,17 +188,6 @@
     }
 
     /**
-     * Set the sleep power draw in milliwatts
-     *
-     * @param sleepPowerDrawMw - sleep power draw in milliwatts.
-     *
-     * @hide
-     */
-    public void setSleepPowerDrawMw(float sleepPowerDrawMw) {
-        mSleepPowerDrawMw = sleepPowerDrawMw;
-    }
-
-    /**
      * get the peak powe draw of the hub. This is the power consumed
      * by the hub at maximum load.
      *
@@ -288,18 +198,6 @@
     }
 
     /**
-     * set the peak power draw of the hub
-     *
-     * @param peakPowerDrawMw - peak power draw of the hub in
-     *                        milliwatts.
-     *
-     * @hide
-     */
-    public void setPeakPowerDrawMw(float peakPowerDrawMw) {
-        mPeakPowerDrawMw = peakPowerDrawMw;
-    }
-
-    /**
      * get the sensors supported by this hub
      *
      * @return int[] - all the supported sensors on this hub
@@ -322,46 +220,65 @@
     }
 
     /**
-     * set the supported sensors on this hub
+     * @return the CHRE platform ID as defined in chre/version.h
      *
-     * @param supportedSensors - supported sensors on this hub
-     *
+     * TODO(b/67734082): Expose as public API
      * @hide
      */
-    public void setSupportedSensors(int[] supportedSensors) {
-        mSupportedSensors = Arrays.copyOf(supportedSensors, supportedSensors.length);
+    public long getChrePlatformId() {
+        return mChrePlatformId;
     }
 
     /**
-     * set memory regions for this hub
+     * @return the CHRE API's major version as defined in chre/version.h
      *
-     * @param memoryRegions - memory regions information
-     *
-     * @see MemoryRegion
-     *
+     * TODO(b/67734082): Expose as public API
      * @hide
      */
-    public void setMemoryRegions(MemoryRegion[] memoryRegions) {
-        mMemoryRegions = Arrays.copyOf(memoryRegions, memoryRegions.length);
+    public byte getChreApiMajorVersion() {
+        return mChreApiMajorVersion;
+    }
+
+    /**
+     * @return the CHRE API's minor version as defined in chre/version.h
+     *
+     * TODO(b/67734082): Expose as public API
+     * @hide
+     */
+    public byte getChreApiMinorVersion() {
+        return mChreApiMinorVersion;
+    }
+
+    /**
+     * @return the CHRE patch version as defined in chre/version.h
+     *
+     * TODO(b/67734082): Expose as public API
+     * @hide
+     */
+    public short getChrePatchVersion() {
+        return mChrePatchVersion;
     }
 
     @Override
     public String toString() {
-      String retVal = "";
-      retVal += "Id : " + mId;
-      retVal += ", Name : " + mName;
-      retVal += "\n\tVendor : " + mVendor;
-      retVal += ", ToolChain : " + mToolchain;
-      retVal += "\n\tPlatformVersion : " + mPlatformVersion;
-      retVal += ", StaticSwVersion : " + mStaticSwVersion;
-      retVal += "\n\tPeakMips : " + mPeakMips;
-      retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
-      retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
-      retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
-      retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
-      retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
+        String retVal = "";
+        retVal += "Id : " + mId;
+        retVal += ", Name : " + mName;
+        retVal += "\n\tVendor : " + mVendor;
+        retVal += ", Toolchain : " + mToolchain;
+        retVal += ", Toolchain version: 0x" + Integer.toHexString(mToolchainVersion);
+        retVal += "\n\tPlatformVersion : 0x" + Integer.toHexString(mPlatformVersion);
+        retVal += ", SwVersion : "
+                + mChreApiMajorVersion + "." + mChreApiMinorVersion + "." + mChrePatchVersion;
+        retVal += ", CHRE platform ID: 0x" + Long.toHexString(mChrePlatformId);
+        retVal += "\n\tPeakMips : " + mPeakMips;
+        retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
+        retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
+        retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
+        retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
+        retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
 
-      return retVal;
+        return retVal;
     }
 
     private ContextHubInfo(Parcel in) {
@@ -371,12 +288,15 @@
         mToolchain = in.readString();
         mPlatformVersion = in.readInt();
         mToolchainVersion = in.readInt();
-        mStaticSwVersion = in.readInt();
         mPeakMips = in.readFloat();
         mStoppedPowerDrawMw = in.readFloat();
         mSleepPowerDrawMw = in.readFloat();
         mPeakPowerDrawMw = in.readFloat();
         mMaxPacketLengthBytes = in.readInt();
+        mChrePlatformId = in.readLong();
+        mChreApiMajorVersion = in.readByte();
+        mChreApiMinorVersion = in.readByte();
+        mChrePatchVersion = (short) in.readInt();
 
         int numSupportedSensors = in.readInt();
         mSupportedSensors = new int[numSupportedSensors];
@@ -395,12 +315,15 @@
         out.writeString(mToolchain);
         out.writeInt(mPlatformVersion);
         out.writeInt(mToolchainVersion);
-        out.writeInt(mStaticSwVersion);
         out.writeFloat(mPeakMips);
         out.writeFloat(mStoppedPowerDrawMw);
         out.writeFloat(mSleepPowerDrawMw);
         out.writeFloat(mPeakPowerDrawMw);
         out.writeInt(mMaxPacketLengthBytes);
+        out.writeLong(mChrePlatformId);
+        out.writeByte(mChreApiMajorVersion);
+        out.writeByte(mChreApiMinorVersion);
+        out.writeInt(mChrePatchVersion);
 
         out.writeInt(mSupportedSensors.length);
         out.writeIntArray(mSupportedSensors);
diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java
index 8f1a5c4..af9a73c 100644
--- a/core/java/android/net/metrics/WakeupEvent.java
+++ b/core/java/android/net/metrics/WakeupEvent.java
@@ -29,7 +29,7 @@
     public String iface;
     public int uid;
     public int ethertype;
-    public byte[] dstHwAddr;
+    public MacAddress dstHwAddr;
     public String srcIp;
     public String dstIp;
     public int ipNextHeader;
@@ -44,7 +44,7 @@
         j.add(iface);
         j.add("uid: " + Integer.toString(uid));
         j.add("eth=0x" + Integer.toHexString(ethertype));
-        j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr));
+        j.add("dstHw=" + dstHwAddr);
         if (ipNextHeader > 0) {
             j.add("ipNxtHdr=" + ipNextHeader);
             j.add("srcIp=" + srcIp);
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index 1ba9777..23c1f20 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -16,7 +16,6 @@
 
 package android.net.metrics;
 
-import android.net.MacAddress;
 import android.os.Process;
 import android.os.SystemClock;
 import android.util.SparseIntArray;
@@ -80,7 +79,7 @@
                 break;
         }
 
-        switch (MacAddress.macAddressType(ev.dstHwAddr)) {
+        switch (ev.dstHwAddr.addressType()) {
             case UNICAST:
                 l2UnicastCount++;
                 break;
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 07c6055..f2e0bdd 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -22,6 +22,7 @@
 import android.opengl.EGL14;
 import android.os.Build;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.util.Log;
 
 import dalvik.system.VMRuntime;
@@ -29,18 +30,110 @@
 import java.io.File;
 
 /** @hide */
-public final class GraphicsEnvironment {
+public class GraphicsEnvironment {
+
+    private static final GraphicsEnvironment sInstance = new GraphicsEnvironment();
+
+    /**
+     * Returns the shared {@link GraphicsEnvironment} instance.
+     */
+    public static GraphicsEnvironment getInstance() {
+        return sInstance;
+    }
 
     private static final boolean DEBUG = false;
     private static final String TAG = "GraphicsEnvironment";
     private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
 
+    private ClassLoader mClassLoader;
+    private String mLayerPath;
+    private String mDebugLayerPath;
+
+    /**
+     * Set up GraphicsEnvironment
+     */
+    public void setup(Context context) {
+        setupGpuLayers(context);
+        chooseDriver(context);
+    }
+
+    /**
+     * Check whether application is debuggable
+     */
+    private static boolean isDebuggable(Context context) {
+        return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) > 0;
+    }
+
+    /**
+     * Store the layer paths available to the loader.
+     */
+    public void setLayerPaths(ClassLoader classLoader,
+                              String layerPath,
+                              String debugLayerPath) {
+        // We have to store these in the class because they are set up before we
+        // have access to the Context to properly set up GraphicsEnvironment
+        mClassLoader = classLoader;
+        mLayerPath = layerPath;
+        mDebugLayerPath = debugLayerPath;
+    }
+
+    /**
+     * Set up layer search paths for all apps
+     * If debuggable, check for additional debug settings
+     */
+    private void setupGpuLayers(Context context) {
+
+        String layerPaths = "";
+
+        // Only enable additional debug functionality if the following conditions are met:
+        // 1. App is debuggable
+        // 2. ENABLE_GPU_DEBUG_LAYERS is true
+        // 3. Package name is equal to GPU_DEBUG_APP
+
+        if (isDebuggable(context)) {
+
+            int enable = Settings.Global.getInt(context.getContentResolver(),
+                                                Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
+
+            if (enable != 0) {
+
+                String gpuDebugApp = Settings.Global.getString(context.getContentResolver(),
+                                                               Settings.Global.GPU_DEBUG_APP);
+
+                String packageName = context.getPackageName();
+
+                if ((gpuDebugApp != null && packageName != null)
+                        && (!gpuDebugApp.isEmpty() && !packageName.isEmpty())
+                        && gpuDebugApp.equals(packageName)) {
+                    Log.i(TAG, "GPU debug layers enabled for " + packageName);
+
+                    // Prepend the debug layer path as a searchable path.
+                    // This will ensure debug layers added will take precedence over
+                    // the layers specified by the app.
+                    layerPaths = mDebugLayerPath + ":";
+
+                    String layers = Settings.Global.getString(context.getContentResolver(),
+                                                              Settings.Global.GPU_DEBUG_LAYERS);
+
+                    Log.i(TAG, "Debug layer list: " + layers);
+                    if (layers != null && !layers.isEmpty()) {
+                        setDebugLayers(layers);
+                    }
+                }
+            }
+
+        }
+
+        // Include the app's lib directory in all cases
+        layerPaths += mLayerPath;
+
+        setLayerPaths(mClassLoader, layerPaths);
+    }
+
     /**
      * Choose whether the current process should use the builtin or an updated driver.
-     *
-     * @hide
      */
-    public static void chooseDriver(Context context) {
+    private static void chooseDriver(Context context) {
         String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
         if (driverPackageName == null || driverPackageName.isEmpty()) {
             return;
@@ -99,8 +192,6 @@
      * on a separate thread, it can usually be finished well before the UI is ready to be drawn.
      *
      * Should only be called after chooseDriver().
-     *
-     * @hide
      */
     public static void earlyInitEGL() {
         Thread eglInitThread = new Thread(
@@ -124,6 +215,7 @@
         return null;
     }
 
+    private static native void setLayerPaths(ClassLoader classLoader, String layerPaths);
+    private static native void setDebugLayers(String layers);
     private static native void setDriverPath(String path);
-
 }
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index a6c3009..8e33b65 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -16,8 +16,6 @@
 
 package android.os;
 
-import android.os.IStatsCallbacks;
-
 /**
   * Binder interface to communicate with the statistics management service.
   * {@hide}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 5d96fd3..0620fa3 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -35,6 +35,10 @@
      * DO NOT MOVE - UserManager.h depends on the ordering of this function.
      */
     int getCredentialOwnerProfile(int userHandle);
+    int getProfileParentId(int userHandle);
+    /*
+     * END OF DO NOT MOVE
+     */
 
     UserInfo createUser(in String name, int flags);
     UserInfo createProfileForUser(in String name, int flags, int userHandle,
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 10adb5a..36121d4 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -191,6 +191,7 @@
  * {@link #readSparseArray(ClassLoader)}.
  */
 public final class Parcel {
+
     private static final boolean DEBUG_RECYCLE = false;
     private static final boolean DEBUG_ARRAY_MAP = false;
     private static final String TAG = "Parcel";
@@ -209,6 +210,12 @@
 
     private RuntimeException mStack;
 
+    /**
+     * Whether or not to parcel the stack trace of an exception. This has a performance
+     * impact, so should only be included in specific processes and only on debug builds.
+     */
+    private static boolean sParcelExceptionStackTrace;
+
     private static final int POOL_SIZE = 6;
     private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
     private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
@@ -325,6 +332,11 @@
     private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
     private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
 
+    /** Last time exception with a stack trace was written */
+    private static volatile long sLastWriteExceptionStackTrace;
+    /** Used for throttling of writing stack trace, which is costly */
+    private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
+
     @CriticalNative
     private static native long nativeGetBlobAshmemSize(long nativePtr);
 
@@ -1696,6 +1708,11 @@
         }
     }
 
+    /** @hide For debugging purposes */
+    public static void setStackTraceParceling(boolean enabled) {
+        sParcelExceptionStackTrace = enabled;
+    }
+
     /**
      * Special function for writing an exception result at the header of
      * a parcel, to be used when returning an exception from a transaction.
@@ -1753,6 +1770,27 @@
             throw new RuntimeException(e);
         }
         writeString(e.getMessage());
+        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+            sLastWriteExceptionStackTrace = timeNow;
+            final int sizePosition = dataPosition();
+            writeInt(0); // Header size will be filled in later
+            StackTraceElement[] stackTrace = e.getStackTrace();
+            final int truncatedSize = Math.min(stackTrace.length, 5);
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < truncatedSize; i++) {
+                sb.append("\tat ").append(stackTrace[i]).append('\n');
+            }
+            writeString(sb.toString());
+            final int payloadPosition = dataPosition();
+            setDataPosition(sizePosition);
+            // Write stack trace header size. Used in native side to skip the header
+            writeInt(payloadPosition - sizePosition);
+            setDataPosition(payloadPosition);
+        } else {
+            writeInt(0);
+        }
         switch (code) {
             case EX_SERVICE_SPECIFIC:
                 writeInt(((ServiceSpecificException) e).errorCode);
@@ -1818,7 +1856,19 @@
         int code = readExceptionCode();
         if (code != 0) {
             String msg = readString();
-            readException(code, msg);
+            String remoteStackTrace = null;
+            final int remoteStackPayloadSize = readInt();
+            if (remoteStackPayloadSize > 0) {
+                remoteStackTrace = readString();
+            }
+            Exception e = createException(code, msg);
+            // Attach remote stack trace if availalble
+            if (remoteStackTrace != null) {
+                RemoteException cause = new RemoteException(
+                        "Remote stack trace:\n" + remoteStackTrace, null, false, false);
+                e.initCause(cause);
+            }
+            SneakyThrow.sneakyThrow(e);
         }
     }
 
@@ -1863,32 +1913,41 @@
      * @param msg The exception message.
      */
     public final void readException(int code, String msg) {
+        SneakyThrow.sneakyThrow(createException(code, msg));
+    }
+
+    /**
+     * Creates an exception with the given message.
+     *
+     * @param code Used to determine which exception class to throw.
+     * @param msg The exception message.
+     */
+    private Exception createException(int code, String msg) {
         switch (code) {
             case EX_PARCELABLE:
                 if (readInt() > 0) {
-                    SneakyThrow.sneakyThrow(
-                            (Exception) readParcelable(Parcelable.class.getClassLoader()));
+                    return (Exception) readParcelable(Parcelable.class.getClassLoader());
                 } else {
-                    throw new RuntimeException(msg + " [missing Parcelable]");
+                    return new RuntimeException(msg + " [missing Parcelable]");
                 }
             case EX_SECURITY:
-                throw new SecurityException(msg);
+                return new SecurityException(msg);
             case EX_BAD_PARCELABLE:
-                throw new BadParcelableException(msg);
+                return new BadParcelableException(msg);
             case EX_ILLEGAL_ARGUMENT:
-                throw new IllegalArgumentException(msg);
+                return new IllegalArgumentException(msg);
             case EX_NULL_POINTER:
-                throw new NullPointerException(msg);
+                return new NullPointerException(msg);
             case EX_ILLEGAL_STATE:
-                throw new IllegalStateException(msg);
+                return new IllegalStateException(msg);
             case EX_NETWORK_MAIN_THREAD:
-                throw new NetworkOnMainThreadException();
+                return new NetworkOnMainThreadException();
             case EX_UNSUPPORTED_OPERATION:
-                throw new UnsupportedOperationException(msg);
+                return new UnsupportedOperationException(msg);
             case EX_SERVICE_SPECIFIC:
-                throw new ServiceSpecificException(readInt(), msg);
+                return new ServiceSpecificException(readInt(), msg);
         }
-        throw new RuntimeException("Unknown exception code: " + code
+        return new RuntimeException("Unknown exception code: " + code
                 + " msg " + msg);
     }
 
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0fce7a4..5dd8d05 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -387,6 +387,12 @@
     public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6;
 
     /**
+     * Go to sleep reason code: Going to sleep by request of an accessibility service
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
+
+    /**
      * Go to sleep flag: Skip dozing state and directly go to full sleep.
      * @hide
      */
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 6d25fc1..4e8b971 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -30,6 +30,12 @@
         super(message);
     }
 
+    /** @hide */
+    public RemoteException(String message, Throwable cause, boolean enableSuppression,
+            boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
     /** {@hide} */
     public RuntimeException rethrowAsRuntimeException() {
         throw new RuntimeException(this);
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
index ad9fbfb..6a62424 100644
--- a/core/java/android/os/ShellCallback.java
+++ b/core/java/android/os/ShellCallback.java
@@ -105,6 +105,9 @@
     ShellCallback(Parcel in) {
         mLocal = false;
         mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+        if (mShellCallback != null) {
+            Binder.allowBlocking(mShellCallback.asBinder());
+        }
     }
 
     public static final Parcelable.Creator<ShellCallback> CREATOR
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index d75219f..fa05a5e 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -91,7 +91,13 @@
         mCmd = cmd;
         mResultReceiver = resultReceiver;
 
-        if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
+        if (DEBUG) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+            Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
+                    + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
+        }
         int res = -1;
         try {
             res = onCommand(mCmd);
@@ -227,15 +233,19 @@
      * @hide
      */
     public ParcelFileDescriptor openFileForSystem(String path, String mode) {
+        if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
         try {
             ParcelFileDescriptor pfd = getShellCallback().openFile(path,
                     "u:r:system_server:s0", mode);
             if (pfd != null) {
+                if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
                 return pfd;
             }
         } catch (RuntimeException e) {
+            if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
             getErrPrintWriter().println("Failure opening file: " + e.getMessage());
         }
+        if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
         getErrPrintWriter().println("Error: Unable to open file: " + path);
         getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
         return null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6decc30..9945755 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9854,6 +9854,27 @@
         public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger";
 
         /**
+         * Allow GPU debug layers?
+         * 0 = no
+         * 1 = yes
+         * @hide
+         */
+        public static final String ENABLE_GPU_DEBUG_LAYERS = "enable_gpu_debug_layers";
+
+        /**
+         * App allowed to load GPU debug layers
+         * @hide
+         */
+        public static final String GPU_DEBUG_APP = "gpu_debug_app";
+
+        /**
+         * Ordered GPU debug layer list
+         * i.e. <layer1>:<layer2>:...:<layerN>
+         * @hide
+         */
+        public static final String GPU_DEBUG_LAYERS = "gpu_debug_layers";
+
+        /**
          * Control whether the process CPU usage meter should be shown.
          *
          * @deprecated This functionality is no longer available as of
diff --git a/core/java/android/service/autofill/InternalSanitizer.java b/core/java/android/service/autofill/InternalSanitizer.java
index 95d2f66..d77e41e 100644
--- a/core/java/android/service/autofill/InternalSanitizer.java
+++ b/core/java/android/service/autofill/InternalSanitizer.java
@@ -16,6 +16,7 @@
 package android.service.autofill;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcelable;
 import android.view.autofill.AutofillValue;
@@ -32,7 +33,11 @@
     /**
      * Sanitizes an {@link AutofillValue}.
      *
+     * @return sanitized value or {@code null} if value could not be sanitized (for example: didn't
+     * match regex, it's an invalid type, regex failed, etc).
+     *
      * @hide
      */
+    @Nullable
     public abstract AutofillValue sanitize(@NonNull AutofillValue value);
 }
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 9a1dcbb..bc4b3fc 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -613,6 +613,11 @@
          *         usernameId, passwordId);
          * </pre>
          *
+         * <p>The sanitizer can also be used as an alternative for a
+         * {@link #setValidator(Validator) validator}&mdashif any of the {@code ids} is a
+         * {@link #SaveInfo.Builder(int, AutofillId[]) required id} and the {@code sanitizer} fail
+         * for it, then the save UI is not shown.
+         *
          * @param sanitizer an implementation provided by the Android System.
          * @param ids id of fields whose value will be sanitized.
          * @return this builder.
diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java
index 12e85b1..a3a98d8 100644
--- a/core/java/android/service/autofill/TextValueSanitizer.java
+++ b/core/java/android/service/autofill/TextValueSanitizer.java
@@ -19,6 +19,7 @@
 import static android.view.autofill.Helper.sDebug;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -62,24 +63,31 @@
     /** @hide */
     @Override
     @TestApi
+    @Nullable
     public AutofillValue sanitize(@NonNull AutofillValue value) {
         if (value == null) {
             Slog.w(TAG, "sanitize() called with null value");
             return null;
         }
-        if (!value.isText()) return value;
+        if (!value.isText()) {
+            if (sDebug) Slog.d(TAG, "sanitize() called with non-text value: " + value);
+            return null;
+        }
 
         final CharSequence text = value.getTextValue();
 
         try {
             final Matcher matcher = mRegex.matcher(text);
-            if (!matcher.matches()) return value;
+            if (!matcher.matches()) {
+                if (sDebug) Slog.d(TAG, "sanitize(): " + mRegex + " failed for " + value);
+                return null;
+            }
 
             final CharSequence sanitized = matcher.replaceAll(mSubst);
             return AutofillValue.forText(sanitized);
         } catch (Exception e) {
             Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
-            return value;
+            return null;
         }
     }
 
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 2c83fc4..8c90156 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -355,21 +355,21 @@
         final Locale locale = localeFromContext(context);
         final MeasureFormat measureFormat = MeasureFormat.getInstance(
                 locale, MeasureFormat.FormatWidth.SHORT);
-        if (days >= 2) {
+        if (days >= 2 || (days > 0 && hours == 0)) {
             days += (hours+12)/24;
             return measureFormat.format(new Measure(days, MeasureUnit.DAY));
         } else if (days > 0) {
             return measureFormat.formatMeasures(
                     new Measure(days, MeasureUnit.DAY),
                     new Measure(hours, MeasureUnit.HOUR));
-        } else if (hours >= 2) {
+        } else if (hours >= 2 || (hours > 0 && minutes == 0)) {
             hours += (minutes+30)/60;
             return measureFormat.format(new Measure(hours, MeasureUnit.HOUR));
         } else if (hours > 0) {
             return measureFormat.formatMeasures(
                     new Measure(hours, MeasureUnit.HOUR),
                     new Measure(minutes, MeasureUnit.MINUTE));
-        } else if (minutes >= 2) {
+        } else if (minutes >= 2 || (minutes > 0 && seconds == 0)) {
             minutes += (seconds+30)/60;
             return measureFormat.format(new Measure(minutes, MeasureUnit.MINUTE));
         } else if (minutes > 0) {
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index dfe00c9b..1345ddf 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -34,5 +34,11 @@
     public AndroidException(Exception cause) {
         super(cause);
     }
+
+    /** @hide */
+    protected AndroidException(String message, Throwable cause, boolean enableSuppression,
+            boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
 };
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2a27220..29baea1 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.text.TextUtils;
 
 import java.util.Map;
@@ -39,13 +40,24 @@
      * @return true if the flag is enabled (either by default in system, or override by user)
      */
     public static boolean isEnabled(Context context, String feature) {
-        // Tries to get feature flag from system property.
-        // Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
-        String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
+        // Override precedence:
+        // Settings.Global -> sys.fflag.override.* -> sys.fflag.*
+
+        // Step 1: check if feature flag is set in Settings.Global.
+        String value;
+        if (context != null) {
+            value = Settings.Global.getString(context.getContentResolver(), feature);
+            if (!TextUtils.isEmpty(value)) {
+                return Boolean.parseBoolean(value);
+            }
+        }
+
+        // Step 2: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
+        value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
         if (!TextUtils.isEmpty(value)) {
             return Boolean.parseBoolean(value);
         }
-        // Step 2: check if feature flag has any default value. Flag name: sys.fflag.<feature>
+        // Step 3: check if feature flag has any default value. Flag name: sys.fflag.<feature>
         value = SystemProperties.get(FFLAG_PREFIX + feature);
         return Boolean.parseBoolean(value);
     }
@@ -53,7 +65,7 @@
     /**
      * Override feature flag to new state.
      */
-    public static void setEnabled(String feature, boolean enabled) {
+    public static void setEnabled(Context context, String feature, boolean enabled) {
         SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
     }
 
diff --git a/core/java/android/os/IStatsCallbacks.aidl b/core/java/android/util/MutableInt.java
similarity index 65%
rename from core/java/android/os/IStatsCallbacks.aidl
rename to core/java/android/util/MutableInt.java
index 02e7cd3..a3d8606 100644
--- a/core/java/android/os/IStatsCallbacks.aidl
+++ b/core/java/android/util/MutableInt.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * Copyright (C) 2011 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
+ *      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,
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package android.os;
+package android.util;
 
 /**
-  * Callback for Statsd to allow binder calls to clients.
-  * {@hide}
-  */
-interface IStatsCallbacks {
-    void onReceiveLogs(out byte[] log);
+ */
+public final class MutableInt {
+    public int value;
+
+    public MutableInt(int value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/android/os/IStatsCallbacks.aidl b/core/java/android/util/MutableLong.java
similarity index 65%
copy from core/java/android/os/IStatsCallbacks.aidl
copy to core/java/android/util/MutableLong.java
index 02e7cd3..575068e 100644
--- a/core/java/android/os/IStatsCallbacks.aidl
+++ b/core/java/android/util/MutableLong.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * Copyright (C) 2011 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
+ *      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,
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package android.os;
+package android.util;
 
 /**
-  * Callback for Statsd to allow binder calls to clients.
-  * {@hide}
-  */
-interface IStatsCallbacks {
-    void onReceiveLogs(out byte[] log);
+ */
+public final class MutableLong {
+    public long value;
+
+    public MutableLong(long value) {
+        this.value = value;
+    }
 }
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index 55b33a6..2bcd863 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -58,11 +58,12 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    throw new RuntimeException("StatsD service connection lost");
+                    Slog.d(TAG, "Failed to find statsd when adding configuration");
+                    return false;
                 }
                 return service.addConfiguration(configKey, config, pkg, cls);
             } catch (RemoteException e) {
-                Slog.d(TAG, "Failed to connect to statsd when getting data");
+                Slog.d(TAG, "Failed to connect to statsd when adding configuration");
                 return false;
             }
         }
@@ -80,11 +81,12 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    throw new RuntimeException("StatsD service connection lost");
+                    Slog.d(TAG, "Failed to find statsd when removing configuration");
+                    return false;
                 }
                 return service.removeConfiguration(configKey);
             } catch (RemoteException e) {
-                Slog.d(TAG, "Failed to connect to statsd when getting data");
+                Slog.d(TAG, "Failed to connect to statsd when removing configuration");
                 return false;
             }
         }
@@ -102,7 +104,8 @@
             try {
                 IStatsManager service = getIStatsManagerLocked();
                 if (service == null) {
-                    throw new RuntimeException("StatsD service connection lost");
+                    Slog.d(TAG, "Failed to find statsd when getting data");
+                    return null;
                 }
                 return service.getData(configKey);
             } catch (RemoteException e) {
diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java
index 449baca..85b7ec8 100644
--- a/core/java/android/util/proto/ProtoUtils.java
+++ b/core/java/android/util/proto/ProtoUtils.java
@@ -17,6 +17,7 @@
 package android.util.proto;
 
 import android.util.AggStats;
+import android.util.Duration;
 
 /**
  * This class contains a list of helper functions to write common proto in
@@ -36,4 +37,15 @@
         proto.write(AggStats.MAX, max);
         proto.end(aggStatsToken);
     }
+
+    /**
+     * Dump Duration to ProtoOutputStream
+     * @hide
+     */
+    public static void toDuration(ProtoOutputStream proto, long fieldId, long startMs, long endMs) {
+        final long token = proto.start(fieldId);
+        proto.write(Duration.START_MS, startMs);
+        proto.write(Duration.END_MS, endMs);
+        proto.end(token);
+    }
 }
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index 232ff25..defa58e 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -446,49 +446,52 @@
      */
     public static String toString(int gravity) {
         final StringBuilder result = new StringBuilder();
-        if ((gravity & FILL) != 0) {
+        if ((gravity & FILL) == FILL) {
             result.append("FILL").append(' ');
         } else {
-            if ((gravity & FILL_VERTICAL) != 0) {
+            if ((gravity & FILL_VERTICAL) == FILL_VERTICAL) {
                 result.append("FILL_VERTICAL").append(' ');
             } else {
-                if ((gravity & TOP) != 0) {
+                if ((gravity & TOP) == TOP) {
                     result.append("TOP").append(' ');
                 }
-                if ((gravity & BOTTOM) != 0) {
+                if ((gravity & BOTTOM) == BOTTOM) {
                     result.append("BOTTOM").append(' ');
                 }
             }
-            if ((gravity & FILL_HORIZONTAL) != 0) {
+            if ((gravity & FILL_HORIZONTAL) == FILL_HORIZONTAL) {
                 result.append("FILL_HORIZONTAL").append(' ');
             } else {
-                if ((gravity & START) != 0) {
+                if ((gravity & START) == START) {
                     result.append("START").append(' ');
-                } else if ((gravity & LEFT) != 0) {
+                } else if ((gravity & LEFT) == LEFT) {
                     result.append("LEFT").append(' ');
                 }
-                if ((gravity & END) != 0) {
+                if ((gravity & END) == END) {
                     result.append("END").append(' ');
-                } else if ((gravity & RIGHT) != 0) {
+                } else if ((gravity & RIGHT) == RIGHT) {
                     result.append("RIGHT").append(' ');
                 }
             }
         }
-        if ((gravity & CENTER) != 0) {
+        if ((gravity & CENTER) == CENTER) {
             result.append("CENTER").append(' ');
         } else {
-            if ((gravity & CENTER_VERTICAL) != 0) {
+            if ((gravity & CENTER_VERTICAL) == CENTER_VERTICAL) {
                 result.append("CENTER_VERTICAL").append(' ');
             }
-            if ((gravity & CENTER_HORIZONTAL) != 0) {
+            if ((gravity & CENTER_HORIZONTAL) == CENTER_HORIZONTAL) {
                 result.append("CENTER_HORIZONTAL").append(' ');
             }
         }
-        if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+        if (result.length() == 0) {
+            result.append("NO GRAVITY").append(' ');
+        }
+        if ((gravity & DISPLAY_CLIP_VERTICAL) == DISPLAY_CLIP_VERTICAL) {
             result.append("DISPLAY_CLIP_VERTICAL").append(' ');
         }
-        if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
-            result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+        if ((gravity & DISPLAY_CLIP_HORIZONTAL) == DISPLAY_CLIP_HORIZONTAL) {
+            result.append("DISPLAY_CLIP_HORIZONTAL").append(' ');
         }
         result.deleteCharAt(result.length() - 1);
         return result.toString();
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 9515040..c4a7160 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
 import android.graphics.Paint;
 import android.util.SparseIntArray;
@@ -281,12 +280,9 @@
         setTarget(mViewTarget.mRenderNode);
     }
 
-    public void setTarget(Canvas canvas) {
-        if (!(canvas instanceof DisplayListCanvas)) {
-            throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
-        }
-        final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
-        setTarget(recordingCanvas.mNode);
+    /** Sets the animation target to the owning view of the DisplayListCanvas */
+    public void setTarget(DisplayListCanvas canvas) {
+        setTarget(canvas.mNode);
     }
 
     private void setTarget(RenderNode node) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5641009..aa2f1c1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -61,6 +61,8 @@
     private static native long nativeCreateTransaction();
     private static native long nativeGetNativeTransactionFinalizer();
     private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+    private static native void nativeMergeTransaction(long transactionObj,
+            long otherTransactionObj);
     private static native void nativeSetAnimationTransaction(long transactionObj);
 
     private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
@@ -654,6 +656,19 @@
         }
     }
 
+    /**
+     * Merge the supplied transaction in to the deprecated "global" transaction.
+     * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
+     * <p>
+     * This is a utility for interop with legacy-code and will go away with the Global Transaction.
+     */
+    @Deprecated
+    public static void mergeToGlobalTransaction(Transaction t) {
+        synchronized(sGlobalTransaction) {
+            sGlobalTransaction.merge(t);
+        }
+    }
+
     /** end a transaction */
     public static void closeTransaction() {
         closeTransaction(false);
@@ -1368,7 +1383,7 @@
          * Sets the security of the surface.  Setting the flag is equivalent to creating the
          * Surface with the {@link #SECURE} flag.
          */
-        Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+        public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
             sc.checkNotReleased();
             if (isSecure) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
@@ -1449,5 +1464,14 @@
             nativeSetAnimationTransaction(mNativeObject);
             return this;
         }
+
+        /**
+         * Merge the other transaction into this transaction, clearing the
+         * other transaction as if it had been applied.
+         */
+        public Transaction merge(Transaction other) {
+            nativeMergeTransaction(mNativeObject, other.mNativeObject);
+            return this;
+        }
     }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e36a298..19ce43f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4163,6 +4163,11 @@
      */
     private static boolean sUseDefaultFocusHighlight;
 
+    /**
+     * True if zero-sized views can be focused.
+     */
+    private static boolean sCanFocusZeroSized;
+
     private String mTransitionName;
 
     static class TintInfo {
@@ -4745,6 +4750,8 @@
             sUseDefaultFocusHighlight = context.getResources().getBoolean(
                     com.android.internal.R.bool.config_useDefaultFocusHighlight);
 
+            sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P;
+
             sCompatibilityDone = true;
         }
     }
@@ -6952,6 +6959,7 @@
     void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
         if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
             mPrivateFlags &= ~PFLAG_FOCUSED;
+            clearParentsWantFocus();
 
             if (propagate && mParent != null) {
                 mParent.clearChildFocus(this);
@@ -10933,8 +10941,9 @@
      * descendants.
      *
      * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
-     * false), or if it is focusable and it is not focusable in touch mode
-     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+     * false), or if it can't be focused due to other conditions (not focusable in touch mode
+     * ({@link #isFocusableInTouchMode}) while the device is in touch mode, not visible, not
+     * enabled, or has no size).
      *
      * See also {@link #focusSearch(int)}, which is what you call to say that you
      * have focus, and you want your parent to look for the next one.
@@ -10972,17 +10981,17 @@
      */
     @TestApi
     public boolean restoreFocusNotInCluster() {
-        return requestFocus(View.FOCUS_DOWN);
+        return requestFocus();
     }
 
     /**
      * Gives focus to the default-focus view in the view hierarchy that has this view as a root.
-     * If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
+     * If the default-focus view cannot be found, falls back to calling {@link #requestFocus()}.
      *
      * @return Whether this view or one of its descendants actually took focus
      */
     public boolean restoreDefaultFocus() {
-        return requestFocus(View.FOCUS_DOWN);
+        return requestFocus();
     }
 
     /**
@@ -11041,9 +11050,7 @@
 
     private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
         // need to be focusable
-        if ((mViewFlags & FOCUSABLE) != FOCUSABLE
-                || (mViewFlags & VISIBILITY_MASK) != VISIBLE
-                || (mViewFlags & ENABLED_MASK) != ENABLED) {
+        if (!canTakeFocus()) {
             return false;
         }
 
@@ -11058,10 +11065,21 @@
             return false;
         }
 
+        if (!isLaidOut()) {
+            mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        }
+
         handleFocusGainInternal(direction, previouslyFocusedRect);
         return true;
     }
 
+    void clearParentsWantFocus() {
+        if (mParent instanceof View) {
+            ((View) mParent).mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            ((View) mParent).clearParentsWantFocus();
+        }
+    }
+
     /**
      * Call this to try to give focus to a specific view or to one of its descendants. This is a
      * special variant of {@link #requestFocus() } that will allow views that are not focusable in
@@ -13433,6 +13451,13 @@
         mAttachInfo.mUnbufferedDispatchRequested = true;
     }
 
+    private boolean canTakeFocus() {
+        return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
+                && ((mViewFlags & FOCUSABLE) == FOCUSABLE)
+                && ((mViewFlags & ENABLED_MASK) == ENABLED)
+                && (sCanFocusZeroSized || !isLaidOut() || (mBottom > mTop) && (mRight > mLeft));
+    }
+
     /**
      * Set flags controlling behavior of this view.
      *
@@ -13452,6 +13477,7 @@
             return;
         }
         int privateFlags = mPrivateFlags;
+        boolean shouldNotifyFocusableAvailable = false;
 
         // If focusable is auto, update the FOCUSABLE bit.
         int focusableChangedByAuto = 0;
@@ -13490,7 +13516,7 @@
                             || focusableChangedByAuto == 0
                             || viewRootImpl == null
                             || viewRootImpl.mThread == Thread.currentThread()) {
-                        mParent.focusableViewAvailable(this);
+                        shouldNotifyFocusableAvailable = true;
                     }
                 }
             }
@@ -13513,10 +13539,7 @@
                 // about in case nothing has focus.  even if this specific view
                 // isn't focusable, it may contain something that is, so let
                 // the root view try to give this focus if nothing else does.
-                if ((mParent != null) && ((mViewFlags & ENABLED_MASK) == ENABLED)
-                        && (mBottom > mTop) && (mRight > mLeft)) {
-                    mParent.focusableViewAvailable(this);
-                }
+                shouldNotifyFocusableAvailable = true;
             }
         }
 
@@ -13525,17 +13548,18 @@
                 // a view becoming enabled should notify the parent as long as the view is also
                 // visible and the parent wasn't already notified by becoming visible during this
                 // setFlags invocation.
-                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE
-                        && ((changed & VISIBILITY_MASK) == 0)) {
-                    if ((mParent != null) && (mViewFlags & ENABLED_MASK) == ENABLED) {
-                        mParent.focusableViewAvailable(this);
-                    }
-                }
+                shouldNotifyFocusableAvailable = true;
             } else {
                 if (hasFocus()) clearFocus();
             }
         }
 
+        if (shouldNotifyFocusableAvailable) {
+            if (mParent != null && canTakeFocus()) {
+                mParent.focusableViewAvailable(this);
+            }
+        }
+
         /* Check if the GONE bit has changed */
         if ((changed & GONE) != 0) {
             needGlobalAttributesUpdate(false);
@@ -20034,15 +20058,58 @@
             }
         }
 
+        final boolean wasLaidOut = isLaidOut();
+
         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
 
+        if (!wasLaidOut && isFocused()) {
+            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            if (canTakeFocus()) {
+                // We have a robust focus, so parents should no longer be wanting focus.
+                clearParentsWantFocus();
+            } else if (!getViewRootImpl().isInLayout()) {
+                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
+                // layout. In this case, there's no guarantee that parent layouts will be evaluated
+                // and thus the safest action is to clear focus here.
+                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+                clearParentsWantFocus();
+            } else if (!hasParentWantsFocus()) {
+                // original requestFocus was likely on this view directly, so just clear focus
+                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+            }
+            // otherwise, we let parents handle re-assigning focus during their layout passes.
+        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
+            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
+            View focused = findFocus();
+            if (focused != null) {
+                // Try to restore focus as close as possible to our starting focus.
+                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
+                    // Give up and clear focus once we've reached the top-most parent which wants
+                    // focus.
+                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
+                }
+            }
+        }
+
         if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
             mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
             notifyEnterOrExitForAutoFillIfNeeded(true);
         }
     }
 
+    private boolean hasParentWantsFocus() {
+        ViewParent parent = mParent;
+        while (parent instanceof ViewGroup) {
+            ViewGroup pv = (ViewGroup) parent;
+            if ((pv.mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
+                return true;
+            }
+            parent = pv.mParent;
+        }
+        return false;
+    }
+
     /**
      * Called from layout when this view should
      * assign a size and position to each of its children.
@@ -20149,6 +20216,23 @@
             mOverlay.getOverlayView().setRight(newWidth);
             mOverlay.getOverlayView().setBottom(newHeight);
         }
+        // If this isn't laid out yet, focus assignment will be handled during the "deferment/
+        // backtracking" of requestFocus during layout, so don't touch focus here.
+        if (!sCanFocusZeroSized && isLaidOut()) {
+            if (newWidth <= 0 || newHeight <= 0) {
+                if (hasFocus()) {
+                    clearFocus();
+                    if (mParent instanceof ViewGroup) {
+                        ((ViewGroup) mParent).clearFocusedInCluster();
+                    }
+                }
+                clearAccessibilityFocus();
+            } else if (oldWidth <= 0 || oldHeight <= 0) {
+                if (mParent != null && canTakeFocus()) {
+                    mParent.focusableViewAvailable(this);
+                }
+            }
+        }
         rebuildOutline();
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 929beae..d0fcc5d 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3215,22 +3215,31 @@
         }
         int descendantFocusability = getDescendantFocusability();
 
+        boolean result;
         switch (descendantFocusability) {
             case FOCUS_BLOCK_DESCENDANTS:
-                return super.requestFocus(direction, previouslyFocusedRect);
+                result = super.requestFocus(direction, previouslyFocusedRect);
+                break;
             case FOCUS_BEFORE_DESCENDANTS: {
                 final boolean took = super.requestFocus(direction, previouslyFocusedRect);
-                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
+                result = took ? took : onRequestFocusInDescendants(direction,
+                        previouslyFocusedRect);
+                break;
             }
             case FOCUS_AFTER_DESCENDANTS: {
                 final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
-                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
+                result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
+                break;
             }
             default:
                 throw new IllegalStateException("descendant focusability must be "
                         + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                         + "but is " + descendantFocusability);
         }
+        if (result && !isLaidOut() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
+            mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        }
+        return result;
     }
 
     /**
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index cd1b190..d07b2ac 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -381,4 +381,9 @@
      * Sets callback to DragDropController.
      */
     public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+    /**
+     * @see android.view.IWindowManager#lockNow
+     */
+    public abstract void lockNow();
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 534335b..c192f5c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1663,14 +1663,6 @@
     void writeToProto(ProtoOutputStream proto, long fieldId);
 
     /**
-     * Returns whether a given window type can be magnified.
-     *
-     * @param windowType The window type.
-     * @return True if the window can be magnified.
-     */
-    public boolean canMagnifyWindow(int windowType);
-
-    /**
      * Returns whether a given window type is considered a top level one.
      * A top level window does not have a container, i.e. attached window,
      * or if it has a container it is laid out as a top-level window, not
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 4cf3461..63519a6 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,6 +34,8 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1;
+
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
         this.mPackageInfo = packageInfo;
@@ -83,7 +85,7 @@
      * supported by the current framework version.
      */
     public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
-        return packageInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1;
+        return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK;
     }
 
     public UserInfo getUserInfo() {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 203de9c..e493739 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -29,10 +29,10 @@
 /**
  * Manages settings state for a WebView. When a WebView is first created, it
  * obtains a set of default settings. These default settings will be returned
- * from any getter call. A WebSettings object obtained from
- * WebView.getSettings() is tied to the life of the WebView. If a WebView has
- * been destroyed, any method call on WebSettings will throw an
- * IllegalStateException.
+ * from any getter call. A {@code WebSettings} object obtained from
+ * {@link WebView#getSettings()} is tied to the life of the WebView. If a WebView has
+ * been destroyed, any method call on {@code WebSettings} will throw an
+ * {@link IllegalStateException}.
  */
 // This is an abstract base class: concrete WebViewProviders must
 // create a class derived from this, and return an instance of it in the
@@ -41,13 +41,13 @@
     /**
      * Enum for controlling the layout of html.
      * <ul>
-     *   <li>NORMAL means no rendering changes. This is the recommended choice for maximum
+     *   <li>{@code NORMAL} means no rendering changes. This is the recommended choice for maximum
      *       compatibility across different platforms and Android versions.</li>
-     *   <li>SINGLE_COLUMN moves all content into one column that is the width of the
+     *   <li>{@code SINGLE_COLUMN} moves all content into one column that is the width of the
      *       view.</li>
-     *   <li>NARROW_COLUMNS makes all columns no wider than the screen if possible. Only use
+     *   <li>{@code NARROW_COLUMNS} makes all columns no wider than the screen if possible. Only use
      *       this for API levels prior to {@link android.os.Build.VERSION_CODES#KITKAT}.</li>
-     *   <li>TEXT_AUTOSIZING boosts font size of paragraphs based on heuristics to make
+     *   <li>{@code TEXT_AUTOSIZING} boosts font size of paragraphs based on heuristics to make
      *       the text readable when viewing a wide-viewport layout in the overview mode.
      *       It is recommended to enable zoom support {@link #setSupportZoom} when
      *       using this mode. Supported from API level
@@ -98,9 +98,9 @@
     /**
      * Enum for specifying the WebView's desired density.
      * <ul>
-     *   <li>FAR makes 100% looking like in 240dpi</li>
-     *   <li>MEDIUM makes 100% looking like in 160dpi</li>
-     *   <li>CLOSE makes 100% looking like in 120dpi</li>
+     *   <li>{@code FAR} makes 100% looking like in 240dpi</li>
+     *   <li>{@code MEDIUM} makes 100% looking like in 160dpi</li>
+     *   <li>{@code CLOSE} makes 100% looking like in 120dpi</li>
      * </ul>
      */
     public enum ZoomDensity {
@@ -652,7 +652,7 @@
      * true, {@link WebChromeClient#onCreateWindow} must be implemented by the
      * host application. The default is {@code false}.
      *
-     * @param support whether to suport multiple windows
+     * @param support whether to support multiple windows
      */
     public abstract void setSupportMultipleWindows(boolean support);
 
@@ -665,7 +665,7 @@
     public abstract boolean supportMultipleWindows();
 
     /**
-     * Sets the underlying layout algorithm. This will cause a relayout of the
+     * Sets the underlying layout algorithm. This will cause a re-layout of the
      * WebView. The default is {@link LayoutAlgorithm#NARROW_COLUMNS}.
      *
      * @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value
@@ -1198,7 +1198,7 @@
 
     /**
      * Tells JavaScript to open windows automatically. This applies to the
-     * JavaScript function window.open(). The default is {@code false}.
+     * JavaScript function {@code window.open()}. The default is {@code false}.
      *
      * @param flag {@code true} if JavaScript can open windows automatically
      */
@@ -1208,7 +1208,7 @@
      * Gets whether JavaScript can open windows automatically.
      *
      * @return {@code true} if JavaScript can open windows automatically during
-     *         window.open()
+     *         {@code window.open()}
      * @see #setJavaScriptCanOpenWindowsAutomatically
      */
     public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d477ffd..d0c2d86 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -195,6 +195,27 @@
     private final boolean mHapticTextHandleEnabled;
 
     private final Magnifier mMagnifier;
+    private final Runnable mUpdateMagnifierRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mMagnifier.update();
+        }
+    };
+    // Update the magnifier contents whenever anything in the view hierarchy is updated.
+    // Note: this only captures UI thread-visible changes, so it's a known issue that an animating
+    // VectorDrawable or Ripple animation will not trigger capture, since they're owned by
+    // RenderThread.
+    private final ViewTreeObserver.OnDrawListener mMagnifierOnDrawListener =
+            new ViewTreeObserver.OnDrawListener() {
+        @Override
+        public void onDraw() {
+            if (mMagnifier != null) {
+                // Posting the method will ensure that updating the magnifier contents will
+                // happen right after the rendering of the current frame.
+                mTextView.post(mUpdateMagnifierRunnable);
+            }
+        }
+    };
 
     // Used to highlight a word when it is corrected by the IME
     private CorrectionHighlighter mCorrectionHighlighter;
@@ -415,15 +436,21 @@
         }
 
         final ViewTreeObserver observer = mTextView.getViewTreeObserver();
-        // No need to create the controller.
-        // The get method will add the listener on controller creation.
-        if (mInsertionPointCursorController != null) {
-            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+        if (observer.isAlive()) {
+            // No need to create the controller.
+            // The get method will add the listener on controller creation.
+            if (mInsertionPointCursorController != null) {
+                observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+            }
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.resetTouchOffsets();
+                observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+            }
+            if (FLAG_USE_MAGNIFIER) {
+                observer.addOnDrawListener(mMagnifierOnDrawListener);
+            }
         }
-        if (mSelectionModifierCursorController != null) {
-            mSelectionModifierCursorController.resetTouchOffsets();
-            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
-        }
+
         updateSpellCheckSpans(0, mTextView.getText().length(),
                 true /* create the spell checker if needed */);
 
@@ -472,6 +499,13 @@
             mSpellChecker = null;
         }
 
+        if (FLAG_USE_MAGNIFIER) {
+            final ViewTreeObserver observer = mTextView.getViewTreeObserver();
+            if (observer.isAlive()) {
+                observer.removeOnDrawListener(mMagnifierOnDrawListener);
+            }
+        }
+
         hideCursorAndSpanControllers();
         stopTextActionModeWithPreservingSelection();
     }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e330916..a2c55b0 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -90,6 +90,31 @@
  * another process. The hierarchy is inflated from a layout resource
  * file, and this class provides some basic operations for modifying
  * the content of the inflated hierarchy.
+ *
+ * <p>{@code RemoteViews} is limited to support for the following layouts:</p>
+ * <ul>
+ *   <li>{@link android.widget.AdapterViewFlipper}</li>
+ *   <li>{@link android.widget.FrameLayout}</li>
+ *   <li>{@link android.widget.GridLayout}</li>
+ *   <li>{@link android.widget.GridView}</li>
+ *   <li>{@link android.widget.LinearLayout}</li>
+ *   <li>{@link android.widget.ListView}</li>
+ *   <li>{@link android.widget.RelativeLayout}</li>
+ *   <li>{@link android.widget.StackView}</li>
+ *   <li>{@link android.widget.ViewFlipper}</li>
+ * </ul>
+ * <p>And the following widgets:</p>
+ * <ul>
+ *   <li>{@link android.widget.AnalogClock}</li>
+ *   <li>{@link android.widget.Button}</li>
+ *   <li>{@link android.widget.Chronometer}</li>
+ *   <li>{@link android.widget.ImageButton}</li>
+ *   <li>{@link android.widget.ImageView}</li>
+ *   <li>{@link android.widget.ProgressBar}</li>
+ *   <li>{@link android.widget.TextClock}</li>
+ *   <li>{@link android.widget.TextView}</li>
+ * </ul>
+ * <p>Descendants of these classes are not supported.</p>
  */
 public class RemoteViews implements Parcelable, Filter {
 
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f..a1e6fd8 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -43,6 +43,7 @@
 
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.List;
 
@@ -118,6 +119,17 @@
             return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
         }
 
+        public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
+            final long[] apkHandles = new long[1];
+            final String path = lite.baseCodePath;
+            apkHandles[0] = nativeOpenApkFd(fd, path);
+            if (apkHandles[0] == 0) {
+                throw new IOException("Unable to open APK " + path + " from fd " + fd);
+            }
+
+            return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+        }
+
         Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
                 boolean debuggable) {
             this.apkHandles = apkHandles;
@@ -152,6 +164,7 @@
     }
 
     private static native long nativeOpenApk(String path);
+    private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath);
     private static native void nativeClose(long handle);
 
     private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 59a7995..e765ab1 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -42,6 +42,7 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.Objects;
 import java.util.UUID;
@@ -383,9 +384,15 @@
 
     public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
             throws IOException {
+        return calculateInstalledSize(pkg, abiOverride, null);
+    }
+
+    public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
+            FileDescriptor fd) throws IOException {
         NativeLibraryHelper.Handle handle = null;
         try {
-            handle = NativeLibraryHelper.Handle.create(pkg);
+            handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
+                    : NativeLibraryHelper.Handle.create(pkg);
             return calculateInstalledSize(pkg, handle, abiOverride);
         } finally {
             IoUtils.closeQuietly(handle);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 91bc681..22bfcc3 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -30,6 +30,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -134,6 +135,13 @@
     }
 
     /**
+     * Checks if given map is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable Map<?, ?> map) {
+        return map == null || map.isEmpty();
+    }
+
+    /**
      * Checks if given array is null or has zero elements.
      */
     public static <T> boolean isEmpty(@Nullable T[] array) {
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index f6741c3..f79a7f5 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -18,34 +18,33 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.PixelCopy;
+import android.view.Surface;
+import android.view.SurfaceView;
 import android.view.View;
-import android.view.ViewRootImpl;
 import android.widget.ImageView;
 import android.widget.PopupWindow;
 
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 
-import java.util.Timer;
-import java.util.TimerTask;
-
 /**
  * Android magnifier widget. Can be used by any view which is attached to window.
  */
 public final class Magnifier {
-    private static final String LOG_TAG = "magnifier";
-    private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
-    // The view for which this magnifier is attached.
+    // Use this to specify that a previous configuration value does not exist.
+    private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
+    // The view to which this magnifier is attached.
     private final View mView;
     // The window containing the magnifier.
     private final PopupWindow mWindow;
@@ -64,8 +63,12 @@
     private final Handler mPixelCopyHandler = Handler.getMain();
     // Current magnification scale.
     private final float mZoomScale;
-    // Timer used to schedule the copy task.
-    private Timer mTimer;
+    // Variables holding previous states, used for detecting redundant calls and invalidation.
+    private final Point mPrevStartCoordsInSurface = new Point(
+            NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
+    private final PointF mPrevPosInView = new PointF(
+            NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
+    private final Rect mPixelCopyRequestRect = new Rect();
 
     /**
      * Initializes a magnifier.
@@ -91,8 +94,8 @@
         mWindow.setTouchable(false);
         mWindow.setBackgroundDrawable(null);
 
-        final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
-        final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
+        final int bitmapWidth = Math.round(mWindowWidth / mZoomScale);
+        final int bitmapHeight = Math.round(mWindowHeight / mZoomScale);
         mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
         getImageView().setImageBitmap(mBitmap);
     }
@@ -106,32 +109,29 @@
      *        relative to the view. The lower end is clamped to 0
      */
     public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
-        if (xPosInView < 0) {
-            xPosInView = 0;
-        }
-
-        if (yPosInView < 0) {
-            yPosInView = 0;
-        }
+        xPosInView = Math.max(0, xPosInView);
+        yPosInView = Math.max(0, yPosInView);
 
         configureCoordinates(xPosInView, yPosInView);
 
-        if (mTimer == null) {
-            mTimer = new Timer();
-            mTimer.schedule(new TimerTask() {
-                @Override
-                public void run() {
-                    performPixelCopy();
-                }
-            }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
-        }
+        // Clamp startX value to avoid distorting the rendering of the magnifier content.
+        final int startX = Math.max(0, Math.min(
+                mCenterZoomCoords.x - mBitmap.getWidth() / 2,
+                mView.getWidth() - mBitmap.getWidth()));
+        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
 
-        if (mWindow.isShowing()) {
-            mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
-                    mWindow.getHeight());
-        } else {
-            mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
-                    mWindowCoords.x, mWindowCoords.y);
+        if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+            performPixelCopy(startX, startY);
+
+            mPrevPosInView.x = xPosInView;
+            mPrevPosInView.y = yPosInView;
+
+            if (mWindow.isShowing()) {
+                mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
+                        mWindow.getHeight());
+            } else {
+                mWindow.showAtLocation(mView, Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y);
+            }
         }
     }
 
@@ -140,11 +140,18 @@
      */
     public void dismiss() {
         mWindow.dismiss();
+    }
 
-        if (mTimer != null) {
-            mTimer.cancel();
-            mTimer.purge();
-            mTimer = null;
+    /**
+     * Forces the magnifier to update its content. It uses the previous coordinates passed to
+     * {@link #show(float, float)}. This only happens if the magnifier is currently showing.
+     *
+     * @hide
+     */
+    public void update() {
+        if (mWindow.isShowing()) {
+            // Update the contents shown in the magnifier.
+            performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y);
         }
     }
 
@@ -170,13 +177,22 @@
     }
 
     private void configureCoordinates(float xPosInView, float yPosInView) {
-        final int[] coordinatesOnScreen = new int[2];
-        mView.getLocationOnScreen(coordinatesOnScreen);
-        final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
-        final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
+        final float posX;
+        final float posY;
 
-        mCenterZoomCoords.x = (int) posXOnScreen;
-        mCenterZoomCoords.y = (int) posYOnScreen;
+        if (mView instanceof SurfaceView) {
+            // No offset required if the backing Surface matches the size of the SurfaceView.
+            posX = xPosInView;
+            posY = yPosInView;
+        } else {
+            final int[] coordinatesInSurface = new int[2];
+            mView.getLocationInSurface(coordinatesInSurface);
+            posX = xPosInView + coordinatesInSurface[0];
+            posY = yPosInView + coordinatesInSurface[1];
+        }
+
+        mCenterZoomCoords.x = Math.round(posX);
+        mCenterZoomCoords.y = Math.round(posY);
 
         final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
                 R.dimen.magnifier_offset);
@@ -184,34 +200,36 @@
         mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
     }
 
-    private void performPixelCopy() {
-        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
-        int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+    private void performPixelCopy(final int startXInSurface, final int startYInSurface) {
+        final Surface surface = getValidViewSurface();
+        if (surface != null) {
+            mPixelCopyRequestRect.set(startXInSurface, startYInSurface,
+                    startXInSurface + mBitmap.getWidth(), startYInSurface + mBitmap.getHeight());
 
-        // Clamp startX value to avoid distorting the rendering of the magnifier content.
-        if (rawStartX < 0) {
-            rawStartX = 0;
-        } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
-            rawStartX = mView.getWidth() - mBitmap.getWidth();
-        }
-
-        final int startX = rawStartX;
-        final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
-
-        if (viewRootImpl != null && viewRootImpl.mSurface != null
-                && viewRootImpl.mSurface.isValid()) {
-            PixelCopy.request(
-                    viewRootImpl.mSurface,
-                    new Rect(startX, startY, startX + mBitmap.getWidth(),
-                            startY + mBitmap.getHeight()),
-                    mBitmap,
-                    result -> getImageView().invalidate(),
+            PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap,
+                    result -> {
+                        getImageView().invalidate();
+                        mPrevStartCoordsInSurface.x = startXInSurface;
+                        mPrevStartCoordsInSurface.y = startYInSurface;
+                    },
                     mPixelCopyHandler);
-        } else {
-            Log.d(LOG_TAG, "Could not perform PixelCopy request");
         }
     }
 
+    @Nullable
+    private Surface getValidViewSurface() {
+        final Surface surface;
+        if (mView instanceof SurfaceView) {
+            surface = ((SurfaceView) mView).getHolder().getSurface();
+        } else if (mView.getViewRootImpl() != null) {
+            surface = mView.getViewRootImpl().mSurface;
+        } else {
+            surface = null;
+        }
+
+        return (surface != null && surface.isValid()) ? surface : null;
+    }
+
     private ImageView getImageView() {
         return mWindow.getContentView().findViewById(R.id.magnifier_image);
     }
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 7abc76a..408a4e9 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
             if (vScroll == 0 && hScroll == 0) {
                 return false;
             }
-            mRecyclerView.scrollBy(hScroll, vScroll);
+            mRecyclerView.smoothScrollBy(hScroll, vScroll);
             return true;
         }
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3552e43..9d036dc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -42,7 +42,6 @@
         "com_google_android_gles_jni_EGLImpl.cpp",
         "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
         "android_app_Activity.cpp",
-        "android_app_ApplicationLoaders.cpp",
         "android_app_NativeActivity.cpp",
         "android_app_admin_SecurityLog.cpp",
         "android_opengl_EGL14.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8977891..2a65cde 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -187,7 +187,6 @@
 extern int register_android_app_backup_FullBackup(JNIEnv *env);
 extern int register_android_app_Activity(JNIEnv *env);
 extern int register_android_app_ActivityThread(JNIEnv *env);
-extern int register_android_app_ApplicationLoaders(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
 extern int register_android_media_RemoteDisplay(JNIEnv *env);
 extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
@@ -1456,7 +1455,6 @@
     REG_JNI(register_android_app_backup_FullBackup),
     REG_JNI(register_android_app_Activity),
     REG_JNI(register_android_app_ActivityThread),
-    REG_JNI(register_android_app_ApplicationLoaders),
     REG_JNI(register_android_app_NativeActivity),
     REG_JNI(register_android_util_jar_StrictJarFile),
     REG_JNI(register_android_view_InputChannel),
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 1a19a40..dd3e6f0 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -44,10 +44,10 @@
 
 struct NativeFamilyBuilder {
     NativeFamilyBuilder(uint32_t langId, int variant)
-        : langId(langId), variant(static_cast<minikin::FontVariant>(variant)),
+        : langId(langId), variant(static_cast<minikin::FontFamily::Variant>(variant)),
           allowUnsupportedFont(false) {}
     uint32_t langId;
-    minikin::FontVariant variant;
+    minikin::FontFamily::Variant variant;
     bool allowUnsupportedFont;
     std::vector<minikin::Font> fonts;
     std::vector<minikin::FontVariation> axes;
@@ -141,7 +141,7 @@
     }
 
     builder->fonts.push_back(minikin::Font(minikinFont,
-            minikin::FontStyle(weight, static_cast<minikin::FontSlant>(italic))));
+            minikin::FontStyle(weight, static_cast<minikin::FontStyle::Slant>(italic))));
     builder->axes.clear();
     return true;
 }
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 5f32d37..1e7f5f5 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -581,7 +581,7 @@
         // restore the original settings.
         paint->setTextSkewX(saveSkewX);
         paint->setFakeBoldText(savefakeBold);
-        if (paint->getFontVariant() == minikin::FontVariant::ELEGANT) {
+        if (paint->getFamilyVariant() == minikin::FontFamily::Variant::ELEGANT) {
             SkScalar size = paint->getTextSize();
             metrics->fTop = -size * kElegantTop / 2048;
             metrics->fBottom = -size * kElegantBottom / 2048;
@@ -880,12 +880,13 @@
 
     static jboolean isElegantTextHeight(jlong paintHandle) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        return obj->getFontVariant() == minikin::FontVariant::ELEGANT;
+        return obj->getFamilyVariant() == minikin::FontFamily::Variant::ELEGANT;
     }
 
     static void setElegantTextHeight(jlong paintHandle, jboolean aa) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        obj->setFontVariant(aa ? minikin::FontVariant::ELEGANT : minikin::FontVariant::DEFAULT);
+        obj->setFamilyVariant(
+                aa ? minikin::FontFamily::Variant::ELEGANT : minikin::FontFamily::Variant::DEFAULT);
     }
 
     static jfloat getTextSize(jlong paintHandle) {
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 3e4073f..d67c0b0 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -83,7 +83,7 @@
 
 static jint Typeface_getWeight(JNIEnv* env, jobject obj, jlong faceHandle) {
     Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
-    return face->fStyle.weight;
+    return face->fStyle.weight();
 }
 
 static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
diff --git a/core/jni/android_app_ApplicationLoaders.cpp b/core/jni/android_app_ApplicationLoaders.cpp
deleted file mode 100644
index 8bbf24a..0000000
--- a/core/jni/android_app_ApplicationLoaders.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 "ApplicationLoaders"
-
-#include <nativehelper/ScopedUtfChars.h>
-#include <nativeloader/native_loader.h>
-#include <vulkan/vulkan_loader_data.h>
-
-#include "core_jni_helpers.h"
-
-static void setupVulkanLayerPath_native(JNIEnv* env, jobject clazz,
-        jobject classLoader, jstring librarySearchPath) {
-    android_namespace_t* ns = android::FindNamespaceByClassLoader(env, classLoader);
-    ScopedUtfChars layerPathChars(env, librarySearchPath);
-
-    vulkan::LoaderData& loader_data = vulkan::LoaderData::GetInstance();
-    if (loader_data.layer_path.empty()) {
-        loader_data.layer_path = layerPathChars.c_str();
-        loader_data.app_namespace = ns;
-    } else {
-        ALOGV("Vulkan layer search path already set, not clobbering with '%s' for namespace %p'",
-                layerPathChars.c_str(), ns);
-    }
-}
-
-static const JNINativeMethod g_methods[] = {
-    { "setupVulkanLayerPath", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
-      reinterpret_cast<void*>(setupVulkanLayerPath_native) },
-};
-
-static const char* const kApplicationLoadersName = "android/app/ApplicationLoaders";
-
-namespace android
-{
-
-int register_android_app_ApplicationLoaders(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, kApplicationLoadersName, g_methods, NELEM(g_methods));
-}
-
-} // namespace android
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index f749488..4ecfd4b 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -18,6 +18,7 @@
 
 #include <graphicsenv/GraphicsEnv.h>
 #include <nativehelper/ScopedUtfChars.h>
+#include <nativeloader/native_loader.h>
 #include "core_jni_helpers.h"
 
 namespace {
@@ -27,8 +28,23 @@
     android::GraphicsEnv::getInstance().setDriverPath(pathChars.c_str());
 }
 
+void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) {
+    android_namespace_t* appNamespace = android::FindNamespaceByClassLoader(env, classLoader);
+    ScopedUtfChars layerPathsChars(env, layerPaths);
+    android::GraphicsEnv::getInstance().setLayerPaths(appNamespace, layerPathsChars.c_str());
+}
+
+void setDebugLayers_native(JNIEnv* env, jobject clazz, jstring layers) {
+    if (layers != nullptr) {
+        ScopedUtfChars layersChars(env, layers);
+        android::GraphicsEnv::getInstance().setDebugLayers(layersChars.c_str());
+    }
+}
+
 const JNINativeMethod g_methods[] = {
     { "setDriverPath", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPath) },
+    { "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) },
+    { "setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native) },
 };
 
 const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment";
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index cfeba83..bb1bfad 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -311,6 +311,14 @@
     transaction->apply(sync);
 }
 
+static void nativeMergeTransaction(JNIEnv* env, jclass clazz,
+        jlong transactionObj, jlong otherTransactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    auto otherTransaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(
+            otherTransactionObj);
+    transaction->merge(std::move(*otherTransaction));
+}
+
 static void nativeSetAnimationTransaction(JNIEnv* env, jclass clazz, jlong transactionObj) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     transaction->setAnimationTransaction();
@@ -882,6 +890,8 @@
             (void*)nativeApplyTransaction },
     {"nativeGetNativeTransactionFinalizer", "()J",
             (void*)nativeGetNativeTransactionFinalizer },
+    {"nativeMergeTransaction", "(JJ)V",
+            (void*)nativeMergeTransaction },
     {"nativeSetAnimationTransaction", "(J)V",
             (void*)nativeSetAnimationTransaction },
     {"nativeSetLayer", "(JJI)V",
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index fce5dd5..17b98da 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -557,6 +557,23 @@
     return reinterpret_cast<jlong>(zipFile);
 }
 
+static jlong
+com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
+        jobject fileDescriptor, jstring debugPathName)
+{
+    ScopedUtfChars debugFilePath(env, debugPathName);
+
+    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+    if (fd < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+        return 0;
+    }
+
+    ZipFileRO* zipFile = ZipFileRO::openFd(fd, debugFilePath.c_str());
+
+    return reinterpret_cast<jlong>(zipFile);
+}
+
 static void
 com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
 {
@@ -567,6 +584,9 @@
     {"nativeOpenApk",
             "(Ljava/lang/String;)J",
             (void *)com_android_internal_content_NativeLibraryHelper_openApk},
+    {"nativeOpenApkFd",
+            "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
+            (void *)com_android_internal_content_NativeLibraryHelper_openApkFd},
     {"nativeClose",
             "(J)V",
             (void *)com_android_internal_content_NativeLibraryHelper_close},
diff --git a/core/proto/android/app/notification.proto b/core/proto/android/app/notification.proto
new file mode 100644
index 0000000..5376b0e
--- /dev/null
+++ b/core/proto/android/app/notification.proto
@@ -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.
+ */
+
+syntax = "proto2";
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.Notification object.
+ * Deprecated fields are not included in the proto.
+ */
+message NotificationProto {
+    optional string channel_id = 1;
+    optional bool has_ticker_text = 2;
+    optional int32 flags = 3;
+    optional int32 color = 4;
+    optional string category = 5;
+    optional string group_key = 6;
+    optional string sort_key = 7;
+    optional int32 action_length = 8;
+
+    // If this field is not set, then the value is unknown.
+    enum Visibility {
+        VISIBILITY_SECRET = -1;
+        VISIBILITY_PRIVATE = 0;
+        VISIBILITY_PUBLIC = 1;
+    }
+    optional Visibility visibility = 9;
+    optional NotificationProto public_version = 10;
+}
diff --git a/core/proto/android/os/cpufreq.proto b/core/proto/android/os/cpufreq.proto
new file mode 100644
index 0000000..a8da0bf
--- /dev/null
+++ b/core/proto/android/os/cpufreq.proto
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_outer_classname = "CpuFreqProto";
+
+package android.os;
+
+// cpu frequency time from /sys/devices/system/cpu/cpufreq/all_time_in_state
+message CpuFreq {
+
+    optional int32 jiffy_hz = 1; // obtain by system config.
+
+    repeated CpuFreqStats cpu_freqs = 2;
+}
+
+// frequency time pre cpu, unit in jiffy, TODO: obtain jiffies.
+message CpuFreqStats {
+
+    optional string cpu_name = 1;
+
+    message TimeInState {
+        optional int32 state_khz = 1;  // cpu frequency
+        optional int64 time_jiffy = 2; // number of jiffies
+    }
+    repeated TimeInState times = 2;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index f68f3a4..ecdabcf 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -20,6 +20,7 @@
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 import "frameworks/base/libs/incident/proto/android/section.proto";
+import "frameworks/base/core/proto/android/os/cpufreq.proto";
 import "frameworks/base/core/proto/android/os/cpuinfo.proto";
 import "frameworks/base/core/proto/android/os/incidentheader.proto";
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
@@ -78,6 +79,10 @@
         (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
     ];
 
+    optional CpuFreq cpu_freq = 2004 [
+        (section).type = SECTION_FILE,
+        (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state"
+    ];
 
     // System Services
     optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
@@ -143,7 +148,11 @@
         (section).args = "activity --proto broadcasts"
     ];
 
-    optional com.android.server.am.proto.ServiceProto amservices = 3014;
+    optional com.android.server.am.proto.ActiveServicesProto amservices = 3014 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "activity --proto service"
+    ];
+
     optional com.android.server.am.proto.ProcessProto amprocesses = 3015;
 
     optional com.android.server.AlarmManagerServiceProto alarm = 3016 [
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index 764288c..5d5aea2 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -305,6 +305,9 @@
     optional SettingProto preferred_network_mode = 226;
     optional SettingProto debug_app = 227;
     optional SettingProto wait_for_debugger = 228;
+    optional SettingProto enable_gpu_debug_layers = 342;
+    optional SettingProto gpu_debug_app = 343;
+    optional SettingProto gpu_debug_layers = 344;
     optional SettingProto low_power_mode = 229;
     optional SettingProto low_power_mode_trigger_level = 230;
     optional SettingProto always_finish_activities = 231;
@@ -384,7 +387,7 @@
     optional SettingProto enable_deletion_helper_no_threshold_toggle = 340;
     optional SettingProto notification_snooze_options = 341;
 
-    // Next tag = 342;
+    // Next tag = 345;
 }
 
 message SecureSettingsProto {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c57cb72..889842c 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -15,11 +15,14 @@
  */
 
 syntax = "proto2";
+
+import "frameworks/base/core/proto/android/app/notification.proto";
 import "frameworks/base/core/proto/android/content/intent.proto";
 import "frameworks/base/core/proto/android/server/intentresolver.proto";
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/graphics/rect.proto";
 import "frameworks/base/core/proto/android/os/looper.proto";
+import "frameworks/base/core/proto/android/util/common.proto";
 
 package com.android.server.am.proto;
 
@@ -30,11 +33,12 @@
 
   optional BroadcastProto broadcasts = 2;
 
-  optional ServiceProto services = 3;
+  optional ActiveServicesProto services = 3;
 
   optional ProcessProto processes = 4;
 }
 
+// "dumpsys activity --proto activities"
 message ActivityStackSupervisorProto {
   optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
   repeated ActivityDisplayProto displays = 2;
@@ -90,6 +94,7 @@
   optional bool keyguard_occluded = 2;
 }
 
+// "dumpsys activity --proto broadcasts"
 message BroadcastProto {
   repeated ReceiverListProto  receiver_list = 1;
 
@@ -164,10 +169,158 @@
   repeated StickyAction actions = 2;
 }
 
-message ServiceProto {
-  // TODO: "dumpsys activity --proto services"
+// "dumpsys activity --proto service"
+message ActiveServicesProto {
+
+  message ServicesByUser {
+    optional int32 user_id = 1;
+    repeated ServiceRecordProto service_records = 2;
+  }
+  repeated ServicesByUser services_by_users = 1;
 }
 
+// corresponds to ActivityManagerService.GrantUri Java class
+message GrantUriProto {
+  optional int32 source_user_id = 1;
+  optional string uri = 2;
+}
+
+message NeededUriGrantsProto {
+  optional string target_package = 1;
+  optional int32 target_uid = 2;
+  optional int32 flags = 3;
+
+  repeated GrantUriProto grants = 4;
+}
+
+message UriPermissionOwnerProto {
+  optional string owner = 1;
+  repeated GrantUriProto read_perms = 2;
+  repeated GrantUriProto write_perms = 3;
+}
+
+message ServiceRecordProto {
+  optional string short_name = 1;
+  optional string hex_hash = 2;
+  optional bool is_running = 3; // false if the application service is null
+  optional int32 pid = 4;
+  optional .android.content.IntentProto intent = 5;
+  optional string package_name = 6;
+  optional string process_name = 7;
+  optional string permission = 8;
+
+  message AppInfo {
+    optional string base_dir = 1;
+    optional string res_dir = 2;
+    optional string data_dir = 3;
+  }
+  optional AppInfo appinfo = 9;
+  optional ProcessRecordProto app = 10;
+  optional ProcessRecordProto isolated_proc = 11;
+  optional bool whitelist_manager = 12;
+  optional bool delayed = 13;
+
+  message Foreground {
+    optional int32 id = 1;
+    optional .android.app.NotificationProto notification = 2;
+  }
+  optional Foreground foreground = 14;
+
+  optional .android.util.Duration create_real_time = 15;
+  optional .android.util.Duration starting_bg_timeout = 16;
+  optional .android.util.Duration last_activity_time = 17;
+  optional .android.util.Duration restart_time = 18;
+  optional bool created_from_fg = 19;
+
+  // variables used to track states related to service start
+  message Start {
+    optional bool start_requested = 1;
+    optional bool delayed_stop = 2;
+    optional bool stop_if_killed = 3;
+    optional bool call_start = 4;
+    optional int32 last_start_id = 5;
+  }
+  optional Start start = 20;
+
+  message ExecuteNesting {
+    optional int32 execute_nesting = 1;
+    optional bool execute_fg = 2;
+    optional .android.util.Duration executing_start = 3;
+  }
+  optional ExecuteNesting execute = 21;
+
+  optional .android.util.Duration destory_time = 22;
+
+  message Crash {
+    optional int32 restart_count = 1;
+    optional .android.util.Duration restart_delay = 2;
+    optional .android.util.Duration next_restart_time = 3;
+    optional int32 crash_count = 4;
+  }
+  optional Crash crash = 23;
+
+  message StartItemProto {
+    optional int32 id = 1;
+    optional .android.util.Duration duration = 2;
+    optional int32 delivery_count = 3;
+    optional int32 done_executing_count = 4;
+    optional .android.content.IntentProto intent = 5;
+    optional NeededUriGrantsProto needed_grants = 6;
+    optional UriPermissionOwnerProto uri_permissions = 7;
+  }
+  repeated StartItemProto delivered_starts = 24;
+  repeated StartItemProto pending_starts = 25;
+
+  repeated IntentBindRecordProto bindings = 26;
+  repeated ConnectionRecordProto connections = 27;
+}
+
+message ConnectionRecordProto {
+  optional string hex_hash = 1;
+  optional int32 user_id = 2;
+
+  enum Flag {
+    AUTO_CREATE = 0;
+    DEBUG_UNBIND = 1;
+    NOT_FG = 2;
+    IMPORTANT_BG = 3;
+    ABOVE_CLIENT = 4;
+    ALLOW_OOM_MANAGEMENT = 5;
+    WAIVE_PRIORITY = 6;
+    IMPORTANT = 7;
+    ADJUST_WITH_ACTIVITY = 8;
+    FG_SERVICE_WHILE_WAKE = 9;
+    FG_SERVICE = 10;
+    TREAT_LIKE_ACTIVITY = 11;
+    VISIBLE = 12;
+    SHOWING_UI = 13;
+    NOT_VISIBLE = 14;
+    DEAD = 15;
+  }
+  repeated Flag flags = 3;
+  optional string service_name = 4;
+  optional string conn_hex_hash = 5;
+}
+
+message AppBindRecordProto {
+  optional string hex_hash = 1;
+  optional ProcessRecordProto client = 2;
+  repeated ConnectionRecordProto connections = 3;
+}
+
+message IntentBindRecordProto {
+  optional string hex_hash = 1;
+  optional bool is_create = 2;
+  optional .android.content.IntentProto intent = 3;
+  optional string binder = 4;
+  optional bool requested = 5;
+  optional bool received = 6;
+  optional bool has_bound = 7;
+  optional bool do_rebind = 8;
+
+  repeated AppBindRecordProto apps = 9;
+}
+
+// TODO: "dumpsys activity --proto processes"
 message ProcessProto {
-  // TODO: "dumpsys activity --proto processes"
 }
diff --git a/core/proto/android/util/common.proto b/core/proto/android/util/common.proto
index 429c3cad..308ef70 100644
--- a/core/proto/android/util/common.proto
+++ b/core/proto/android/util/common.proto
@@ -30,3 +30,13 @@
 
     optional int64 max = 3;
 }
+
+/**
+ * Very basic data structure to represent Duration.
+ */
+message Duration {
+
+    optional int64 start_ms = 1;
+
+    optional int64 end_ms = 2;
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86103e4..feef5ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3958,6 +3958,10 @@
         <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
-    </application>
+
+        <service android:name="com.android.server.display.BrightnessIdleJob"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+</application>
 
 </manifest>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index dddd52b..cea9cd7 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -28,7 +28,7 @@
     <string name="unknownName" msgid="6867811765370350269">"غير معروف"</string>
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"البريد الصوتي"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
-    <string name="mmiError" msgid="5154499457739052907">"‏حدثت مشكلة في الاتصال أو أن كود MMI غير صحيح."</string>
+    <string name="mmiError" msgid="5154499457739052907">"‏حدثت مشكلة في الاتصال أو أن رمز MMI غير صحيح."</string>
     <string name="mmiFdnError" msgid="5224398216385316471">"تم تقييد التشغيل لأرقام الاتصال الثابت فقط."</string>
     <string name="mmiErrorWhileRoaming" msgid="762488890299284230">"يتعذر تغيير إعدادات إعادة توجيه المكالمات من هاتفك أثناء التجوال."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"تم تمكين الخدمة."</string>
@@ -43,7 +43,7 @@
     <string name="mismatchPin" msgid="609379054496863419">"أرقام التعريف الشخصية التي كتبتها غير مطابقة."</string>
     <string name="invalidPin" msgid="3850018445187475377">"ادخل رقم تعريف شخصي مكون من ٤ إلى ٨ أرقام."</string>
     <string name="invalidPuk" msgid="8761456210898036513">"‏اكتب رمز PUK مكونًا من ٨ أرقام أو أكثر."</string>
-    <string name="needPuk" msgid="919668385956251611">"‏شريحة SIM مؤمّنة بكود PUK. اكتب كود PUK لإلغاء تأمينها."</string>
+    <string name="needPuk" msgid="919668385956251611">"‏شريحة SIM مؤمّنة برمز PUK. اكتب رمز PUK لإلغاء تأمينها."</string>
     <string name="needPuk2" msgid="4526033371987193070">"‏اكتب PUK2 لإلغاء تأمين شريحة SIM."</string>
     <string name="enablePin" msgid="209412020907207950">"‏محاولة غير ناجحة، مكّن قفل SIM/RUIM."</string>
     <plurals name="pinpuk_attempts" formatted="false" msgid="1251012001539225582">
@@ -139,8 +139,8 @@
     <string name="cfTemplateForwardedTime" msgid="9206251736527085256">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> بعد <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانية"</string>
     <string name="cfTemplateRegistered" msgid="5073237827620166285">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: لم تتم إعادة التوجيه"</string>
     <string name="cfTemplateRegisteredTime" msgid="6781621964320635172">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: لم تتم إعادة التوجيه"</string>
-    <string name="fcComplete" msgid="3118848230966886575">"اكتمل كود الميزة."</string>
-    <string name="fcError" msgid="3327560126588500777">"حدثت مشكلة بالاتصال أو أن كود الميزة غير صحيح."</string>
+    <string name="fcComplete" msgid="3118848230966886575">"اكتمل رمز الميزة."</string>
+    <string name="fcError" msgid="3327560126588500777">"حدثت مشكلة بالاتصال أو أن رمز الميزة غير صحيح."</string>
     <string name="httpErrorOk" msgid="1191919378083472204">"حسنًا"</string>
     <string name="httpError" msgid="7956392511146698522">"حدث خطأ في الشبكة."</string>
     <string name="httpErrorLookup" msgid="4711687456111963163">"‏تعذر العثور على عنوان URL."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 05ab42d..658e951 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1169,7 +1169,7 @@
     <string name="sim_done_button" msgid="827949989369963775">"Terminé"</string>
     <string name="sim_added_title" msgid="3719670512889674693">"Carte SIM ajoutée."</string>
     <string name="sim_added_message" msgid="6599945301141050216">"Redémarrez votre appareil pour accéder au réseau mobile."</string>
-    <string name="sim_restart_button" msgid="4722407842815232347">"Recommencer"</string>
+    <string name="sim_restart_button" msgid="4722407842815232347">"Redémarrer"</string>
     <string name="carrier_app_dialog_message" msgid="7066156088266319533">"Pour que la nouvelle carte SIM fonctionne correctement, vous devez installer et ouvrir une application fournie par votre fournisseur de services."</string>
     <string name="carrier_app_dialog_button" msgid="7900235513678617329">"TÉLÉCHARGER L\'APPLICATION"</string>
     <string name="carrier_app_dialog_not_now" msgid="6361378684292268027">"PAS MAINTENANT"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index cea220a..c866c4c 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -172,7 +172,7 @@
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Wasifu wako wa kazini haupatikani tena kwenye kifaa hiki"</string>
     <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Umejaribu kuweka nenosiri mara nyingi mno"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Kifaa kinadhibitiwa"</string>
-    <string name="network_logging_notification_text" msgid="7930089249949354026">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gonga ili upate maelezo zaidi."</string>
+    <string name="network_logging_notification_text" msgid="7930089249949354026">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gusa ili upate maelezo zaidi."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Data iliyomo kwenye kifaa chako itafutwa"</string>
     <string name="factory_reset_message" msgid="7972496262232832457">"Huwezi kutumia programu ya msimamizi. Sasa data iliyo kwenye kifaa chako itafutwa.\n\nIkiwa una maswali yoyote, wasiliana na msimamizi wa shirika lako."</string>
     <string name="me" msgid="6545696007631404292">"Mimi"</string>
@@ -251,7 +251,7 @@
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Programu zinazotumia betri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
-    <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Gonga ili upate maelezo kuhusu betri na matumizi ya data"</string>
+    <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
     <string name="foreground_service_multiple_separator" msgid="4021901567939866542">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mtindo salama"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Mfumo wa Android"</string>
@@ -807,7 +807,7 @@
     <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Bakia kwenye Ukurasa huu"</string>
     <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nJe, una uhakika unataka kutoka kwenye ukurasa huu?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Thibitisha"</string>
-    <string name="double_tap_toast" msgid="4595046515400268881">"Kidokezo: Gonga mara mbili ili kukuza ndani na nje."</string>
+    <string name="double_tap_toast" msgid="4595046515400268881">"Kidokezo: Gusa mara mbili ili kukuza ndani na nje."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"Kujaza kiotomatiki"</string>
     <string name="setup_autofill" msgid="7103495070180590814">"Weka uwezo wa kujaza kiotomatiki"</string>
     <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
@@ -971,7 +971,7 @@
     <string name="undo" msgid="7905788502491742328">"Tendua"</string>
     <string name="redo" msgid="7759464876566803888">"Rejesha"</string>
     <string name="autofill" msgid="3035779615680565188">"Kujaza kiotomatiki"</string>
-    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Uchaguzi wa maandishi?"</string>
+    <string name="textSelectionCABTitle" msgid="5236850394370820357">"Maandishi yaliyoteuliwa"</string>
     <string name="addToDictionary" msgid="4352161534510057874">"Ongeza kwenye kamusi"</string>
     <string name="deleteText" msgid="6979668428458199034">"Futa"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Mbinu ya uingizaji"</string>
@@ -986,7 +986,7 @@
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Baadhi ya vipengee vya mfumo huenda visifanye kazi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Hifadhi haitoshi kwa ajili ya mfumo. Hakikisha una MB 250 za nafasi ya hifadhi isiyotumika na uanzishe upya."</string>
     <string name="app_running_notification_title" msgid="8718335121060787914">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumiwa"</string>
-    <string name="app_running_notification_text" msgid="1197581823314971177">"Gonga ili upate maelezo zaidi au usitishe programu."</string>
+    <string name="app_running_notification_text" msgid="1197581823314971177">"Gusa ili upate maelezo zaidi au usitishe programu."</string>
     <string name="ok" msgid="5970060430562524910">"Sawa"</string>
     <string name="cancel" msgid="6442560571259935130">"Ghairi"</string>
     <string name="yes" msgid="5362982303337969312">"Sawa"</string>
@@ -1062,7 +1062,7 @@
     <string name="android_upgrading_starting_apps" msgid="451464516346926713">"Programu zinaanza"</string>
     <string name="android_upgrading_complete" msgid="1405954754112999229">"Inamaliza kuwasha."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> inaendelea"</string>
-    <string name="heavy_weight_notification_detail" msgid="867643381388543170">"Gonga ili uende kwenye programu"</string>
+    <string name="heavy_weight_notification_detail" msgid="867643381388543170">"Gusa ili uende kwenye programu"</string>
     <string name="heavy_weight_switcher_title" msgid="7153167085403298169">"Badilisha programu?"</string>
     <string name="heavy_weight_switcher_text" msgid="7022631924534406403">"Programmu nyingine tayari inaendeshwa na lazima hiyo ikomeshwe kabla ya kuanza nyingine mpya."</string>
     <string name="old_app_action" msgid="493129172238566282">"Rejea katika <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
@@ -1107,7 +1107,7 @@
     <string name="wifi_available_title_connecting" msgid="1557292688310330032">"Inaunganisha kwenye mtandao wa Wi‑Fi unaotumiwa na mtu yeyote"</string>
     <string name="wifi_available_title_connected" msgid="7542672851522241548">"Imeunganisha kwenye mtandao wa Wi-Fi"</string>
     <string name="wifi_available_title_failed_to_connect" msgid="6861772233582618132">"Imeshindwa kuunganisha kwenye mtandao wa Wi‑Fi"</string>
-    <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gonga ili uone mitandao yote"</string>
+    <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gusa ili uone mitandao yote"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Unganisha"</string>
     <string name="wifi_available_action_all_networks" msgid="1100098935861622985">"Mitandao Yote"</string>
     <string name="wifi_available_sign_in" msgid="9157196203958866662">"Ingia kwa mtandao wa Wi-Fi"</string>
@@ -1115,7 +1115,7 @@
     <!-- no translation found for network_available_sign_in_detailed (8000081941447976118) -->
     <skip />
     <string name="wifi_no_internet" msgid="8451173622563841546">"Wi-Fi haina muunganisho wa intaneti"</string>
-    <string name="wifi_no_internet_detailed" msgid="8083079241212301741">"Gonga ili upate chaguo"</string>
+    <string name="wifi_no_internet_detailed" msgid="8083079241212301741">"Gusa ili upate chaguo"</string>
     <string name="network_switch_metered" msgid="4671730921726992671">"Sasa inatumia <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
     <string name="network_switch_metered_detail" msgid="5325661434777870353">"Kifaa hutumia <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wakati <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> haina Intaneti. Huenda ukalipishwa."</string>
     <string name="network_switch_metered_toast" msgid="5779283181685974304">"Imebadilisha mtandao kutoka <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na sasa inatumia <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
@@ -1136,7 +1136,7 @@
     <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Anzisha Wi-Fi Moja kwa Moja. Hii itazima mteja/mtandao-hewa wa Wi-Fi."</string>
     <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Haikuweza kuanzisha Wi-Fi Moja kwa Moja."</string>
     <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi ya Moja kwa Moja imewashwa"</string>
-    <string name="wifi_p2p_enabled_notification_message" msgid="8064677407830620023">"Gonga ili uweke mipangilio"</string>
+    <string name="wifi_p2p_enabled_notification_message" msgid="8064677407830620023">"Gusa ili uweke mipangilio"</string>
     <string name="accept" msgid="1645267259272829559">"Kubali"</string>
     <string name="decline" msgid="2112225451706137894">"Kataa"</string>
     <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Mwaliko umetumwa"</string>
@@ -1172,7 +1172,7 @@
     <string name="carrier_app_dialog_button" msgid="7900235513678617329">"PATA PROGRAMU"</string>
     <string name="carrier_app_dialog_not_now" msgid="6361378684292268027">"SIYO SASA"</string>
     <string name="carrier_app_notification_title" msgid="8921767385872554621">"SIM mpya imewekwa"</string>
-    <string name="carrier_app_notification_text" msgid="1132487343346050225">"Gonga ili uiweke"</string>
+    <string name="carrier_app_notification_text" msgid="1132487343346050225">"Gusa ili uiweke"</string>
     <string name="time_picker_dialog_title" msgid="8349362623068819295">"Weka saa"</string>
     <string name="date_picker_dialog_title" msgid="5879450659453782278">"Weka tarehe"</string>
     <string name="date_time_set" msgid="5777075614321087758">"Weka"</string>
@@ -1188,11 +1188,11 @@
     <string name="usb_ptp_notification_title" msgid="1347328437083192112">"USB kwa ajili ya kuhamisha picha"</string>
     <string name="usb_midi_notification_title" msgid="4850904915889144654">"USB kwa ajili ya MIDI"</string>
     <string name="usb_accessory_notification_title" msgid="7848236974087653666">"Imeunganishwa kwa kifuasi cha USB"</string>
-    <string name="usb_notification_message" msgid="3370903770828407960">"Gonga ili upate chaguo zaidi."</string>
+    <string name="usb_notification_message" msgid="3370903770828407960">"Gusa ili upate chaguo zaidi."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Imetambua kifaa cha sauti ya analogi"</string>
-    <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Kifaa ulichoambatisha hakitumiki kwenye simu hii. Gonga ili upate maelezo zaidi."</string>
+    <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Kifaa ulichoambatisha hakitumiki kwenye simu hii. Gusa ili upate maelezo zaidi."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Utatuaji wa USB umeunganishwa"</string>
-    <string name="adb_active_notification_message" msgid="4948470599328424059">"Gonga ili uzime utatuaji wa USB."</string>
+    <string name="adb_active_notification_message" msgid="4948470599328424059">"Gusa ili uzime utatuaji wa USB."</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Chagua ili kulemaza utatuaji USB."</string>
     <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"Inatayarisha ripoti ya hitilafu…"</string>
     <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"Ungependa kushiriki ripoti ya hitilafu?"</string>
@@ -1204,23 +1204,23 @@
     <string name="show_ime" msgid="2506087537466597099">"Ionyeshe kwenye skrini wakati kibodi halisi inatumika"</string>
     <string name="hardware" msgid="194658061510127999">"Onyesha kibodi pepe"</string>
     <string name="select_keyboard_layout_notification_title" msgid="597189518763083494">"Sanidi kibodi halisi"</string>
-    <string name="select_keyboard_layout_notification_message" msgid="8084622969903004900">"Gonga ili uchague lugha na muundo"</string>
+    <string name="select_keyboard_layout_notification_message" msgid="8084622969903004900">"Gusa ili uchague lugha na muundo"</string>
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"Onyesha juu ya programu zingine"</string>
     <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> inachomoza juu ya programu zingine"</string>
     <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> inachomoza juu ya programu zingine."</string>
-    <string name="alert_windows_notification_message" msgid="8917232109522912560">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> kutumia kipengele hiki, gonga ili ufungue mipangilio na ukizime."</string>
+    <string name="alert_windows_notification_message" msgid="8917232109522912560">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> kutumia kipengele hiki, gusa ili ufungue mipangilio na ukizime."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="3367294525884949878">"ZIMA"</string>
     <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"Inaandaa <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_checking_notification_message" msgid="4747432538578886744">"Inakagua hitilafu"</string>
     <string name="ext_media_new_notification_message" msgid="7589986898808506239">"<xliff:g id="NAME">%s</xliff:g> mpya imegunduliwa"</string>
     <string name="ext_media_ready_notification_message" msgid="4083398150380114462">"Kwa ajili ya kuhamisha picha na maudhui"</string>
     <string name="ext_media_unmountable_notification_title" msgid="8295123366236989588">"<xliff:g id="NAME">%s</xliff:g> iliyoharibika"</string>
-    <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> ina hitilafu. Gonga ili uirekebishe."</string>
+    <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> ina hitilafu. Gusa ili uirekebishe."</string>
     <string name="ext_media_unmountable_notification_message" product="tv" msgid="3941179940297874950">"<xliff:g id="NAME">%s</xliff:g> imeharibika. Ichague ili uirekebishe."</string>
     <string name="ext_media_unsupported_notification_title" msgid="3797642322958803257">"<xliff:g id="NAME">%s</xliff:g> isiyotumika"</string>
-    <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"Kifaa hiki hakitumii <xliff:g id="NAME">%s</xliff:g>. Gonga ili uweke mipangilio ya muundo unaoweza kutumika."</string>
+    <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"Kifaa hiki hakitumii <xliff:g id="NAME">%s</xliff:g>. Gusa ili uweke mipangilio ya muundo unaoweza kutumika."</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"Kifaa hiki hakitumii <xliff:g id="NAME">%s</xliff:g> hii. Ichague ili uweke muundo unaotumika."</string>
     <string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"<xliff:g id="NAME">%s</xliff:g> imeondolewa bila kutarajiwa"</string>
     <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"Ondoa <xliff:g id="NAME">%s</xliff:g> kabla ya kuchomoa ili kuepuka kupoteza data"</string>
@@ -1261,7 +1261,7 @@
     <string name="permdesc_requestDeletePackages" msgid="3406172963097595270">"Huruhusu programu kuomba idhini ya kufuta vifurushi."</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"omba kupuuza uimarishji wa betri"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Huruhusu programu kuomba ruhusa ya kupuuza uimarishaji wa betri katika programu yako."</string>
-    <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Gonga mara mbili kwa udhibiti wa kuza"</string>
+    <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Gusa mara mbili kwa udhibiti wa kuza"</string>
     <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Haikuweza kuongeza wijeti."</string>
     <string name="ime_action_go" msgid="8320845651737369027">"Nenda"</string>
     <string name="ime_action_search" msgid="658110271822807811">"Tafuta"</string>
@@ -1292,8 +1292,8 @@
     <string name="notification_ranker_binding_label" msgid="774540592299064747">"Huduma ya kupanga arifa"</string>
     <string name="vpn_title" msgid="19615213552042827">"VPN imewezeshwa"</string>
     <string name="vpn_title_long" msgid="6400714798049252294">"VPN imeamilishwa na <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="vpn_text" msgid="1610714069627824309">"Gonga ili kudhibiti mtandao."</string>
-    <string name="vpn_text_long" msgid="4907843483284977618">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gonga ili kudhibiti mtandao"</string>
+    <string name="vpn_text" msgid="1610714069627824309">"Gusa ili kudhibiti mtandao."</string>
+    <string name="vpn_text_long" msgid="4907843483284977618">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gusa ili kudhibiti mtandao"</string>
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kila mara VPN iliyowashwa inaunganishwa…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Kila mara VPN iliyowashwa imeunganishwa"</string>
     <string name="vpn_lockdown_disconnected" msgid="735805531187559719">"Imeondolewa kwenye VPN iliyowashwa kila wakati"</string>
@@ -1304,9 +1304,9 @@
     <string name="reset" msgid="2448168080964209908">"Weka upya"</string>
     <string name="submit" msgid="1602335572089911941">"Wasilisha"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mtindo wa gari umewezeshwa"</string>
-    <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"Gonga ili ufunge hali ya garini."</string>
+    <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"Gusa ili ufunge hali ya garini."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"Kushiriki au kusambaza intaneti kumewashwa"</string>
-    <string name="tethered_notification_message" msgid="2113628520792055377">"Gonga ili uweke mipangilio."</string>
+    <string name="tethered_notification_message" msgid="2113628520792055377">"Gusa ili uweke mipangilio."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"Umezima kipengele cha kusambaza mtandao"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"Wasiliana na msimamizi wako ili upate maelezo zaidi"</string>
     <string name="back_button_label" msgid="2300470004503343439">"Nyuma"</string>
@@ -1383,7 +1383,7 @@
     <string name="storage_usb" msgid="3017954059538517278">"Hifadhi ya USB"</string>
     <string name="extract_edit_menu_button" msgid="8940478730496610137">"Badilisha"</string>
     <string name="data_usage_warning_title" msgid="3620440638180218181">"Tahadhari ya matumizi ya data"</string>
-    <string name="data_usage_warning_body" msgid="6660692274311972007">"Gonga ili uangalie matumizi na mipangilio."</string>
+    <string name="data_usage_warning_body" msgid="6660692274311972007">"Gusa ili uangalie matumizi na mipangilio."</string>
     <string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Kikomo data ya 2G-3G kimefikiwa"</string>
     <string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Kikomo cha data ya 4G kimefikiwa"</string>
     <string name="data_usage_mobile_limit_title" msgid="6561099244084267376">"Umefikisha kipimo cha juu cha data"</string>
@@ -1395,7 +1395,7 @@
     <string name="data_usage_wifi_limit_snoozed_title" msgid="8743856006384825974">"Taarifa za Wi-fi zimevuka kiwanga"</string>
     <string name="data_usage_limit_snoozed_body" msgid="7035490278298441767">"<xliff:g id="SIZE">%s</xliff:g> juu ya kikomo kilichobainishwa."</string>
     <string name="data_usage_restricted_title" msgid="5965157361036321914">"Data ya mandhari nyuma imezuiwa"</string>
-    <string name="data_usage_restricted_body" msgid="469866376337242726">"Gonga ili uondoe kizuizi."</string>
+    <string name="data_usage_restricted_body" msgid="469866376337242726">"Gusa ili uondoe kizuizi."</string>
     <string name="ssl_certificate" msgid="6510040486049237639">"Cheti cha usalama"</string>
     <string name="ssl_certificate_is_valid" msgid="6825263250774569373">"Cheti hiki ni halali."</string>
     <string name="issued_to" msgid="454239480274921032">"Kimetolewa kwa:"</string>
@@ -1594,7 +1594,7 @@
     <string name="reason_unknown" msgid="6048913880184628119">"haijulikani"</string>
     <string name="reason_service_unavailable" msgid="7824008732243903268">"Huduma ya uchapishaji haijawashwa"</string>
     <string name="print_service_installed_title" msgid="2246317169444081628">"Huduma ya <xliff:g id="NAME">%s</xliff:g> imesakinisha"</string>
-    <string name="print_service_installed_message" msgid="5897362931070459152">"Gonga ili uwashe"</string>
+    <string name="print_service_installed_message" msgid="5897362931070459152">"Gusa ili uwashe"</string>
     <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Weka PIN ya msimamizi"</string>
     <string name="restr_pin_enter_pin" msgid="3395953421368476103">"Ingiza PIN"</string>
     <string name="restr_pin_incorrect" msgid="8571512003955077924">"Sio sahihi"</string>
@@ -1722,12 +1722,12 @@
     <string name="new_sms_notification_title" msgid="8442817549127555977">"Una ujumbe mpya"</string>
     <string name="new_sms_notification_content" msgid="7002938807812083463">"Fungua programu ya SMS ili uweze kuangalia"</string>
     <string name="user_encrypted_title" msgid="9054897468831672082">"Huenda baadhi ya utendaji ukawa vikwazo"</string>
-    <string name="user_encrypted_message" msgid="4923292604515744267">"Gonga ili ufungue"</string>
+    <string name="user_encrypted_message" msgid="4923292604515744267">"Gusa ili ufungue"</string>
     <string name="user_encrypted_detail" msgid="5708447464349420392">"Data ya mtumiaji imefungwa"</string>
     <string name="profile_encrypted_detail" msgid="3700965619978314974">"Wasifu wa kazini umefungwa"</string>
-    <string name="profile_encrypted_message" msgid="6964994232310195874">"Gonga ili ufungue wasifu wa kazini"</string>
+    <string name="profile_encrypted_message" msgid="6964994232310195874">"Gusa ili ufungue wasifu wa kazini"</string>
     <string name="usb_mtp_launch_notification_title" msgid="8359219638312208932">"Imeunganishwa na <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
-    <string name="usb_mtp_launch_notification_description" msgid="8541876176425411358">"Gonga ili uangalie faili"</string>
+    <string name="usb_mtp_launch_notification_description" msgid="8541876176425411358">"Gusa ili uangalie faili"</string>
     <string name="pin_target" msgid="3052256031352291362">"Bandika"</string>
     <string name="unpin_target" msgid="3556545602439143442">"Bandua"</string>
     <string name="app_info" msgid="6856026610594615344">"Maelezo ya programu"</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9e0722b..7a3fa1a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -152,7 +152,7 @@
     <!-- Displayed to tell the user that they should switch their network preference. -->
     <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
     <!-- Displayed to tell the user that they should switch their network preference. -->
-    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings &gt; Network &amp; Internet &gt; Mobile networks &gt; Preferred network type."</string>
+    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings &gt; Network &amp; internet &gt; Mobile networks &gt; Preferred network type."</string>
     <!-- Displayed to tell the user that emergency calls might not be available. -->
     <string name="EmergencyCallWarningTitle">Wi\u2011Fi calling is active</string>
     <!-- Displayed to tell the user that emergency calls might not be available. -->
@@ -3014,7 +3014,7 @@
     <string name="network_available_sign_in_detailed"><xliff:g id="network_ssid">%1$s</xliff:g></string>
 
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's title. -->
-    <string name="wifi_no_internet">Wi-Fi has no Internet access</string>
+    <string name="wifi_no_internet">Wi-Fi has no internet access</string>
 
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
@@ -3023,7 +3023,7 @@
     <string name="network_switch_metered">Switched to <xliff:g id="network_type">%1$s</xliff:g></string>
 
     <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's message. %1$s is the network that the device switched to, e.g., cellular data. %2$s is the network type the device switched from, e.g., Wi-Fi. Both are strings in the network_switch_type_name array. -->
-    <string name="network_switch_metered_detail">Device uses <xliff:g id="new_network">%1$s</xliff:g> when <xliff:g id="previous_network">%2$s</xliff:g> has no Internet access. Charges may apply.</string>
+    <string name="network_switch_metered_detail">Device uses <xliff:g id="new_network">%1$s</xliff:g> when <xliff:g id="previous_network">%2$s</xliff:g> has no internet access. Charges may apply.</string>
 
     <!-- A toast might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the text of the toast. %1$s is the network that the device switched from, e.g., Wi-Fi. %2$s is the network type the device switched from, e.g., cellular data. Both are strings in the network_switch_type_name array. -->
     <string name="network_switch_metered_toast">Switched from <xliff:g id="previous_network">%1$s</xliff:g> to <xliff:g id="new_network">%2$s</xliff:g></string>
@@ -3043,7 +3043,7 @@
      <!-- A notification is shown when a user's selected SSID is later disabled due to connectivity problems.  This is the notification's title / ticker. -->
      <string name="wifi_watchdog_network_disabled">Couldn\'t connect to Wi-Fi</string>
      <!-- A notification is shown when a user's selected SSID is later disabled due to connectivity problems.  The complete alert msg is: <hotspot name> + this string, i.e. "Linksys has a poor internet connection" -->
-    <string name="wifi_watchdog_network_disabled_detailed">\u0020has a poor Internet connection.</string>
+    <string name="wifi_watchdog_network_disabled_detailed">\u0020has a poor internet connection.</string>
 
     <!-- Do not translate. Default access point SSID used for tethering -->
     <string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index d36ed63..0d35f4e 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -356,6 +356,9 @@
                     Settings.Global.USE_GOOGLE_MAIL,
                     Settings.Global.VT_IMS_ENABLED,
                     Settings.Global.WAIT_FOR_DEBUGGER,
+                    Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
+                    Settings.Global.GPU_DEBUG_APP,
+                    Settings.Global.GPU_DEBUG_LAYERS,
                     Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
                     Settings.Global.WARNING_TEMPERATURE,
                     Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 24e3646..dee51dc 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -117,13 +117,13 @@
         assertEquals("2 days", Formatter.formatShortElapsedTime(mContext, 2 * DAY));
         assertEquals("1 day, 23 hr",
                 Formatter.formatShortElapsedTime(mContext, 1 * DAY + 23 * HOUR + 59 * MINUTE));
-        assertEquals("1 day, 0 hr",
+        assertEquals("1 day",
                 Formatter.formatShortElapsedTime(mContext, 1 * DAY + 59 * MINUTE));
-        assertEquals("1 day, 0 hr", Formatter.formatShortElapsedTime(mContext, 1 * DAY));
+        assertEquals("1 day", Formatter.formatShortElapsedTime(mContext, 1 * DAY));
         assertEquals("24 hr", Formatter.formatShortElapsedTime(mContext, 23 * HOUR + 30 * MINUTE));
         assertEquals("3 hr", Formatter.formatShortElapsedTime(mContext, 2 * HOUR + 30 * MINUTE));
         assertEquals("2 hr", Formatter.formatShortElapsedTime(mContext, 2 * HOUR));
-        assertEquals("1 hr, 0 min", Formatter.formatShortElapsedTime(mContext, 1 * HOUR));
+        assertEquals("1 hr", Formatter.formatShortElapsedTime(mContext, 1 * HOUR));
         assertEquals("60 min",
                 Formatter.formatShortElapsedTime(mContext, 59 * MINUTE + 30 * SECOND));
         assertEquals("59 min",
@@ -132,7 +132,7 @@
         assertEquals("2 min", Formatter.formatShortElapsedTime(mContext, 2 * MINUTE));
         assertEquals("1 min, 59 sec",
                 Formatter.formatShortElapsedTime(mContext, 1 * MINUTE + 59 * SECOND + 999));
-        assertEquals("1 min, 0 sec", Formatter.formatShortElapsedTime(mContext, 1 * MINUTE));
+        assertEquals("1 min", Formatter.formatShortElapsedTime(mContext, 1 * MINUTE));
         assertEquals("59 sec", Formatter.formatShortElapsedTime(mContext, 59 * SECOND + 999));
         assertEquals("1 sec", Formatter.formatShortElapsedTime(mContext, 1 * SECOND));
         assertEquals("0 sec", Formatter.formatShortElapsedTime(mContext, 1));
@@ -154,9 +154,9 @@
                 mContext, 2 * DAY));
         assertEquals("1 day, 23 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 1 * DAY + 23 * HOUR + 59 * MINUTE));
-        assertEquals("1 day, 0 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
+        assertEquals("1 day", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 1 * DAY + 59 * MINUTE));
-        assertEquals("1 day, 0 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
+        assertEquals("1 day", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 1 * DAY));
         assertEquals("24 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 23 * HOUR + 30 * MINUTE));
@@ -164,9 +164,9 @@
                 mContext, 2 * HOUR + 30 * MINUTE));
         assertEquals("2 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 2 * HOUR));
-        assertEquals("1 hr, 0 min", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
+        assertEquals("1 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 1 * HOUR));
-        assertEquals("1 hr, 0 min", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
+        assertEquals("1 hr", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 59 * MINUTE + 30 * SECOND));
         assertEquals("59 min", Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                 mContext, 59 * MINUTE));
diff --git a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
index c5d175b..b8c4123 100644
--- a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
+++ b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -51,6 +52,7 @@
     }
 
     private void cleanup() {
+        Settings.Global.putString(mContext.getContentResolver(), TEST_FEATURE_NAME, "");
         SystemProperties.set(FeatureFlagUtils.FFLAG_PREFIX + TEST_FEATURE_NAME, "");
         SystemProperties.set(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + TEST_FEATURE_NAME, "");
     }
@@ -63,7 +65,7 @@
     }
 
     @Test
-    public void testGetFlag_override_shouldReturnTrue() {
+    public void testGetFlag_adb_override_shouldReturnTrue() {
         SystemProperties.set(FeatureFlagUtils.FFLAG_PREFIX + TEST_FEATURE_NAME, "false");
         SystemProperties.set(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + TEST_FEATURE_NAME, "true");
 
@@ -71,10 +73,20 @@
     }
 
     @Test
+    public void testGetFlag_settings_override_shouldReturnTrue() {
+        SystemProperties.set(FeatureFlagUtils.FFLAG_PREFIX + TEST_FEATURE_NAME, "false");
+        SystemProperties.set(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + TEST_FEATURE_NAME, "false");
+
+        Settings.Global.putString(mContext.getContentResolver(), TEST_FEATURE_NAME, "true");
+
+        assertTrue(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME));
+    }
+
+    @Test
     public void testSetEnabled_shouldSetOverrideFlag() {
         assertFalse(FeatureFlagUtils.isEnabled(mContext, TEST_FEATURE_NAME));
 
-        FeatureFlagUtils.setEnabled(TEST_FEATURE_NAME, true);
+        FeatureFlagUtils.setEnabled(null /* context */, TEST_FEATURE_NAME, true);
 
         assertEquals(SystemProperties.get(FeatureFlagUtils.FFLAG_PREFIX + TEST_FEATURE_NAME, null),
                 "");
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 7b2fa76..76eb4e6 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -112,14 +112,29 @@
 # Run sanity tests on fonts on checkbuild
 checkbuild: fontchain_lint
 
-FONTCHAIN_LINTER := frameworks/base/tools/fonts/fontchain_lint.py
+FONTCHAIN_LINTER := $(HOST_OUT_EXECUTABLES)/fontchain_linter
 ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
 CHECK_EMOJI := false
 else
 CHECK_EMOJI := true
 endif
 
+fontchain_lint_timestamp := $(call intermediates-dir-for,PACKAGING,fontchain_lint)/stamp
+
 .PHONY: fontchain_lint
-fontchain_lint: $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img
-	PYTHONPATH=$$PYTHONPATH:external/fonttools/Lib \
-	python $(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
+fontchain_lint: $(fontchain_lint_timestamp)
+
+fontchain_lint_deps := \
+    external/unicode/DerivedAge.txt \
+    external/unicode/emoji-data.txt \
+    external/unicode/emoji-sequences.txt \
+    external/unicode/emoji-variation-sequences.txt \
+    external/unicode/emoji-zwj-sequences.txt \
+    external/unicode/additions/emoji-data.txt \
+    external/unicode/additions/emoji-sequences.txt \
+    external/unicode/additions/emoji-zwj-sequences.txt \
+
+$(fontchain_lint_timestamp): $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img $(fontchain_lint_deps)
+	@echo Running fontchain lint
+	$(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
+	touch $@
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 209f364..dad24da 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -115,10 +115,14 @@
     <family lang="und-Hebr">
         <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
     </family>
     <family lang="und-Thai" variant="elegant">
         <font weight="400" style="normal">NotoSansThai-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifThai-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
     </family>
     <family lang="und-Thai" variant="compact">
         <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font>
@@ -127,14 +131,20 @@
     <family lang="und-Armn">
         <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-Bold.ttf</font>
     </family>
     <family lang="und-Geor und-Geok">
         <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansGeorgian-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifGeorgian-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifGeorgian-Bold.ttf</font>
     </family>
     <family lang="und-Deva" variant="elegant">
         <font weight="400" style="normal">NotoSansDevanagari-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansDevanagari-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifDevanagari-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifDevanagari-Bold.ttf</font>
     </family>
     <family lang="und-Deva" variant="compact">
         <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font>
@@ -147,6 +157,8 @@
     <family lang="und-Gujr" variant="elegant">
         <font weight="400" style="normal">NotoSansGujarati-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifGujarati-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifGujarati-Bold.ttf</font>
     </family>
     <family lang="und-Gujr" variant="compact">
         <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font>
@@ -163,6 +175,8 @@
     <family lang="und-Taml" variant="elegant">
         <font weight="400" style="normal">NotoSansTamil-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansTamil-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifTamil-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifTamil-Bold.ttf</font>
     </family>
     <family lang="und-Taml" variant="compact">
         <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
@@ -171,6 +185,8 @@
     <family lang="und-Mlym" variant="elegant">
         <font weight="400" style="normal">NotoSansMalayalam-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansMalayalam-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMalayalam-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMalayalam-Bold.ttf</font>
     </family>
     <family lang="und-Mlym" variant="compact">
         <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
@@ -179,6 +195,8 @@
     <family lang="und-Beng" variant="elegant">
         <font weight="400" style="normal">NotoSansBengali-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansBengali-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifBengali-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifBengali-Bold.ttf</font>
     </family>
     <family lang="und-Beng" variant="compact">
         <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
@@ -187,6 +205,8 @@
     <family lang="und-Telu" variant="elegant">
         <font weight="400" style="normal">NotoSansTelugu-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansTelugu-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifTelugu-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifTelugu-Bold.ttf</font>
     </family>
     <family lang="und-Telu" variant="compact">
         <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
@@ -195,6 +215,8 @@
     <family lang="und-Knda" variant="elegant">
         <font weight="400" style="normal">NotoSansKannada-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansKannada-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKannada-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKannada-Bold.ttf</font>
     </family>
     <family lang="und-Knda" variant="compact">
         <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
@@ -258,6 +280,8 @@
     <family lang="und-Laoo" variant="elegant">
         <font weight="400" style="normal">NotoSansLao-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifLao-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="compact">
         <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font>
@@ -472,15 +496,19 @@
     </family>
     <family lang="zh-Hans">
         <font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="zh-Hant zh-Bopo">
         <font weight="400" style="normal" index="3">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="ja">
         <font weight="400" style="normal" index="0">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
     <family lang="und-Zsye">
         <font weight="400" style="normal">NotoColorEmoji.ttf</font>
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 3bf4f90..dea194e 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -36,138 +36,69 @@
 
     private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
-    private static final int OPACITY_ENTER_DURATION = 600;
-    private static final int OPACITY_ENTER_DURATION_FAST = 120;
-    private static final int OPACITY_EXIT_DURATION = 480;
+    private static final int OPACITY_DURATION = 80;
 
-    // Hardware rendering properties.
-    private CanvasProperty<Paint> mPropPaint;
-    private CanvasProperty<Float> mPropRadius;
-    private CanvasProperty<Float> mPropX;
-    private CanvasProperty<Float> mPropY;
+    private ObjectAnimator mAnimator;
 
-    // Software rendering properties.
     private float mOpacity = 0;
 
     /** Whether this ripple is bounded. */
     private boolean mIsBounded;
 
-    public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
-            boolean forceSoftware) {
-        super(owner, bounds, forceSoftware);
+    private boolean mFocused = false;
+    private boolean mHovered = false;
+
+    public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded) {
+        super(owner, bounds);
 
         mIsBounded = isBounded;
     }
 
     public boolean isVisible() {
-        return mOpacity > 0 || isHardwareAnimating();
+        return mOpacity > 0;
     }
 
-    @Override
-    protected boolean drawSoftware(Canvas c, Paint p) {
-        boolean hasContent = false;
-
+    public void draw(Canvas c, Paint p) {
         final int origAlpha = p.getAlpha();
-        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+        final int alpha = Math.min((int) (origAlpha * mOpacity + 0.5f), 255);
         if (alpha > 0) {
             p.setAlpha(alpha);
             c.drawCircle(0, 0, mTargetRadius, p);
             p.setAlpha(origAlpha);
-            hasContent = true;
         }
-
-        return hasContent;
     }
 
-    @Override
-    protected boolean drawHardware(DisplayListCanvas c) {
-        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
-        return true;
-    }
-
-    @Override
-    protected Animator createSoftwareEnter(boolean fast) {
-        // Linear enter based on current opacity.
-        final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
-        final int duration = (int) ((1 - mOpacity) * maxDuration);
-
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(duration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
-        return opacity;
-    }
-
-    @Override
-    protected Animator createSoftwareExit() {
-        final AnimatorSet set = new AnimatorSet();
-
-        // Linear exit after enter is completed.
-        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
-        exit.setInterpolator(LINEAR_INTERPOLATOR);
-        exit.setDuration(OPACITY_EXIT_DURATION);
-        exit.setAutoCancel(true);
-
-        final AnimatorSet.Builder builder = set.play(exit);
-
-        // Linear "fast" enter based on current opacity.
-        final int fastEnterDuration = mIsBounded ?
-                (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-        if (fastEnterDuration > 0) {
-            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
-            enter.setInterpolator(LINEAR_INTERPOLATOR);
-            enter.setDuration(fastEnterDuration);
-            enter.setAutoCancel(true);
-
-            builder.after(enter);
+    public void setState(boolean focused, boolean hovered, boolean animateChanged) {
+        if (mHovered != hovered || mFocused != focused) {
+            mHovered = hovered;
+            mFocused = focused;
+            onStateChanged(animateChanged);
         }
-
-        return set;
     }
 
-    @Override
-    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
-        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-
-        final int targetAlpha = p.getAlpha();
-        final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
-        p.setAlpha(currentAlpha);
-
-        mPropPaint = CanvasProperty.createPaint(p);
-        mPropRadius = CanvasProperty.createFloat(mTargetRadius);
-        mPropX = CanvasProperty.createFloat(0);
-        mPropY = CanvasProperty.createFloat(0);
-
-        final int fastEnterDuration = mIsBounded ?
-                (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-
-        // Linear exit after enter is completed.
-        final RenderNodeAnimator exit = new RenderNodeAnimator(
-                mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-        exit.setInterpolator(LINEAR_INTERPOLATOR);
-        exit.setDuration(OPACITY_EXIT_DURATION);
-        if (fastEnterDuration > 0) {
-            exit.setStartDelay(fastEnterDuration);
-            exit.setStartValue(targetAlpha);
+    private void onStateChanged(boolean animateChanged) {
+        float newOpacity = 0.0f;
+        if (mHovered) newOpacity += 1.0f;
+        if (mFocused) newOpacity += 1.0f;
+        if (mAnimator != null) {
+            mAnimator.cancel();
+            mAnimator = null;
         }
-        set.add(exit);
-
-        // Linear "fast" enter based on current opacity.
-        if (fastEnterDuration > 0) {
-            final RenderNodeAnimator enter = new RenderNodeAnimator(
-                    mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
-            enter.setInterpolator(LINEAR_INTERPOLATOR);
-            enter.setDuration(fastEnterDuration);
-            set.add(enter);
+        if (animateChanged) {
+            mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
+            mAnimator.setDuration(OPACITY_DURATION);
+            mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
+            mAnimator.start();
+        } else {
+            mOpacity = newOpacity;
         }
-
-        return set;
     }
 
-    @Override
-    protected void jumpValuesToExit() {
-        mOpacity = 0;
+    public void jumpToFinal() {
+        if (mAnimator != null) {
+            mAnimator.end();
+            mAnimator = null;
+        }
     }
 
     private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java
index e83513c..0e38826 100644
--- a/graphics/java/android/graphics/drawable/RippleComponent.java
+++ b/graphics/java/android/graphics/drawable/RippleComponent.java
@@ -27,23 +27,14 @@
 import java.util.ArrayList;
 
 /**
- * Abstract class that handles hardware/software hand-off and lifecycle for
- * animated ripple foreground and background components.
+ * Abstract class that handles size & positioning common to the ripple & focus states.
  */
 abstract class RippleComponent {
-    private final RippleDrawable mOwner;
+    protected final RippleDrawable mOwner;
 
     /** Bounds used for computing max radius. May be modified by the owner. */
     protected final Rect mBounds;
 
-    /** Whether we can use hardware acceleration for the exit animation. */
-    private boolean mHasDisplayListCanvas;
-
-    private boolean mHasPendingHardwareAnimator;
-    private RenderNodeAnimatorSet mHardwareAnimator;
-
-    private Animator mSoftwareAnimator;
-
     /** Whether we have an explicit maximum radius. */
     private boolean mHasMaxRadius;
 
@@ -53,16 +44,9 @@
     /** Screen density used to adjust pixel-based constants. */
     protected float mDensityScale;
 
-    /**
-     * If set, force all ripple animations to not run on RenderThread, even if it would be
-     * available.
-     */
-    private final boolean mForceSoftware;
-
-    public RippleComponent(RippleDrawable owner, Rect bounds, boolean forceSoftware) {
+    public RippleComponent(RippleDrawable owner, Rect bounds) {
         mOwner = owner;
         mBounds = bounds;
-        mForceSoftware = forceSoftware;
     }
 
     public void onBoundsChange() {
@@ -92,89 +76,6 @@
     }
 
     /**
-     * Starts a ripple enter animation.
-     *
-     * @param fast whether the ripple should enter quickly
-     */
-    public final void enter(boolean fast) {
-        cancel();
-
-        mSoftwareAnimator = createSoftwareEnter(fast);
-
-        if (mSoftwareAnimator != null) {
-            mSoftwareAnimator.start();
-        }
-    }
-
-    /**
-     * Starts a ripple exit animation.
-     */
-    public final void exit() {
-        cancel();
-
-        if (mHasDisplayListCanvas) {
-            // We don't have access to a canvas here, but we expect one on the
-            // next frame. We'll start the render thread animation then.
-            mHasPendingHardwareAnimator = true;
-
-            // Request another frame.
-            invalidateSelf();
-        } else {
-            mSoftwareAnimator = createSoftwareExit();
-            mSoftwareAnimator.start();
-        }
-    }
-
-    /**
-     * Cancels all animations. Software animation values are left in the
-     * current state, while hardware animation values jump to the end state.
-     */
-    public void cancel() {
-        cancelSoftwareAnimations();
-        endHardwareAnimations();
-    }
-
-    /**
-     * Ends all animations, jumping values to the end state.
-     */
-    public void end() {
-        endSoftwareAnimations();
-        endHardwareAnimations();
-    }
-
-    /**
-     * Draws the ripple to the canvas, inheriting the paint's color and alpha
-     * properties.
-     *
-     * @param c the canvas to which the ripple should be drawn
-     * @param p the paint used to draw the ripple
-     * @return {@code true} if something was drawn, {@code false} otherwise
-     */
-    public boolean draw(Canvas c, Paint p) {
-        final boolean hasDisplayListCanvas = !mForceSoftware && c.isHardwareAccelerated()
-                && c instanceof DisplayListCanvas;
-        if (mHasDisplayListCanvas != hasDisplayListCanvas) {
-            mHasDisplayListCanvas = hasDisplayListCanvas;
-
-            if (!hasDisplayListCanvas) {
-                // We've switched from hardware to non-hardware mode. Panic.
-                endHardwareAnimations();
-            }
-        }
-
-        if (hasDisplayListCanvas) {
-            final DisplayListCanvas hw = (DisplayListCanvas) c;
-            startPendingAnimation(hw, p);
-
-            if (mHardwareAnimator != null) {
-                return drawHardware(hw);
-            }
-        }
-
-        return drawSoftware(c, p);
-    }
-
-    /**
      * Populates {@code bounds} with the maximum drawing bounds of the ripple
      * relative to its center. The resulting bounds should be translated into
      * parent drawable coordinates before use.
@@ -186,77 +87,10 @@
         bounds.set(-r, -r, r, r);
     }
 
-    /**
-     * Starts the pending hardware animation, if available.
-     *
-     * @param hw hardware canvas on which the animation should draw
-     * @param p paint whose properties the hardware canvas should use
-     */
-    private void startPendingAnimation(DisplayListCanvas hw, Paint p) {
-        if (mHasPendingHardwareAnimator) {
-            mHasPendingHardwareAnimator = false;
-
-            mHardwareAnimator = createHardwareExit(new Paint(p));
-            mHardwareAnimator.start(hw);
-
-            // Preemptively jump the software values to the end state now that
-            // the hardware exit has read whatever values it needs.
-            jumpValuesToExit();
-        }
-    }
-
-    /**
-     * Cancels any current software animations, leaving the values in their
-     * current state.
-     */
-    private void cancelSoftwareAnimations() {
-        if (mSoftwareAnimator != null) {
-            mSoftwareAnimator.cancel();
-            mSoftwareAnimator = null;
-        }
-    }
-
-    /**
-     * Ends any current software animations, jumping the values to their end
-     * state.
-     */
-    private void endSoftwareAnimations() {
-        if (mSoftwareAnimator != null) {
-            mSoftwareAnimator.end();
-            mSoftwareAnimator = null;
-        }
-    }
-
-    /**
-     * Ends any pending or current hardware animations.
-     * <p>
-     * Hardware animations can't synchronize values back to the software
-     * thread, so there is no "cancel" equivalent.
-     */
-    private void endHardwareAnimations() {
-        if (mHardwareAnimator != null) {
-            mHardwareAnimator.end();
-            mHardwareAnimator = null;
-        }
-
-        if (mHasPendingHardwareAnimator) {
-            mHasPendingHardwareAnimator = false;
-
-            // Manually jump values to their exited state. Normally we'd do that
-            // later when starting the hardware exit, but we're aborting early.
-            jumpValuesToExit();
-        }
-    }
-
     protected final void invalidateSelf() {
         mOwner.invalidateSelf(false);
     }
 
-    protected final boolean isHardwareAnimating() {
-        return mHardwareAnimator != null && mHardwareAnimator.isRunning()
-                || mHasPendingHardwareAnimator;
-    }
-
     protected final void onHotspotBoundsChanged() {
         if (!mHasMaxRadius) {
             final float halfWidth = mBounds.width() / 2.0f;
@@ -276,76 +110,4 @@
     protected void onTargetRadiusChanged(float targetRadius) {
         // Stub.
     }
-
-    protected abstract Animator createSoftwareEnter(boolean fast);
-
-    protected abstract Animator createSoftwareExit();
-
-    protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
-
-    protected abstract boolean drawHardware(DisplayListCanvas c);
-
-    protected abstract boolean drawSoftware(Canvas c, Paint p);
-
-    /**
-     * Called when the hardware exit is cancelled. Jumps software values to end
-     * state to ensure that software and hardware values are synchronized.
-     */
-    protected abstract void jumpValuesToExit();
-
-    public static class RenderNodeAnimatorSet {
-        private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
-
-        public void add(RenderNodeAnimator anim) {
-            mAnimators.add(anim);
-        }
-
-        public void clear() {
-            mAnimators.clear();
-        }
-
-        public void start(DisplayListCanvas target) {
-            if (target == null) {
-                throw new IllegalArgumentException("Hardware canvas must be non-null");
-            }
-
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                anim.setTarget(target);
-                anim.start();
-            }
-        }
-
-        public void cancel() {
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                anim.cancel();
-            }
-        }
-
-        public void end() {
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                anim.end();
-            }
-        }
-
-        public boolean isRunning() {
-            final ArrayList<RenderNodeAnimator> animators = mAnimators;
-            final int N = animators.size();
-            for (int i = 0; i < N; i++) {
-                final RenderNodeAnimator anim = animators.get(i);
-                if (anim.isRunning()) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 1727eca..8b185f2 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,11 +16,6 @@
 
 package android.graphics.drawable;
 
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.ActivityInfo.Config;
@@ -42,6 +37,11 @@
 import android.graphics.Shader;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.Arrays;
 
@@ -135,9 +135,6 @@
     private PorterDuffColorFilter mMaskColorFilter;
     private boolean mHasValidMask;
 
-    /** Whether we expect to draw a background when visible. */
-    private boolean mBackgroundActive;
-
     /** The current ripple. May be actively animating or pending entry. */
     private RippleForeground mRipple;
 
@@ -217,7 +214,7 @@
         }
 
         if (mBackground != null) {
-            mBackground.end();
+            mBackground.jumpToFinal();
         }
 
         cancelExitingRipples();
@@ -266,9 +263,9 @@
             }
         }
 
-        setRippleActive(focused || (enabled && pressed));
+        setRippleActive(enabled && pressed);
 
-        setBackgroundActive(hovered, hovered);
+        setBackgroundActive(hovered, focused);
         return changed;
     }
 
@@ -283,14 +280,13 @@
         }
     }
 
-    private void setBackgroundActive(boolean active, boolean focused) {
-        if (mBackgroundActive != active) {
-            mBackgroundActive = active;
-            if (active) {
-                tryBackgroundEnter(focused);
-            } else {
-                tryBackgroundExit();
-            }
+    private void setBackgroundActive(boolean hovered, boolean focused) {
+        if (mBackground == null && (hovered || focused)) {
+            mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+            mBackground.setup(mState.mMaxRadius, mDensity);
+        }
+        if (mBackground != null) {
+            mBackground.setState(focused, hovered, true);
         }
     }
 
@@ -327,10 +323,6 @@
                 tryRippleEnter();
             }
 
-            if (mBackgroundActive) {
-                tryBackgroundEnter(false);
-            }
-
             // Skip animations, just show the correct final states.
             jumpToCurrentState();
         }
@@ -546,26 +538,6 @@
     }
 
     /**
-     * Creates an active hotspot at the specified location.
-     */
-    private void tryBackgroundEnter(boolean focused) {
-        if (mBackground == null) {
-            final boolean isBounded = isBounded();
-            mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
-        }
-
-        mBackground.setup(mState.mMaxRadius, mDensity);
-        mBackground.enter(focused);
-    }
-
-    private void tryBackgroundExit() {
-        if (mBackground != null) {
-            // Don't null out the background, we need it to draw!
-            mBackground.exit();
-        }
-    }
-
-    /**
      * Attempts to start an enter animation for the active hotspot. Fails if
      * there are too many animating ripples.
      */
@@ -593,7 +565,7 @@
         }
 
         mRipple.setup(mState.mMaxRadius, mDensity);
-        mRipple.enter(false);
+        mRipple.enter();
     }
 
     /**
@@ -623,9 +595,7 @@
         }
 
         if (mBackground != null) {
-            mBackground.end();
-            mBackground = null;
-            mBackgroundActive = false;
+            mBackground.setState(false, false, false);
         }
 
         cancelExitingRipples();
@@ -858,38 +828,8 @@
         final float y = mHotspotBounds.exactCenterY();
         canvas.translate(x, y);
 
-        updateMaskShaderIfNeeded();
-
-        // Position the shader to account for canvas translation.
-        if (mMaskShader != null) {
-            final Rect bounds = getBounds();
-            mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
-            mMaskShader.setLocalMatrix(mMaskMatrix);
-        }
-
-        // Grab the color for the current state and cut the alpha channel in
-        // half so that the ripple and background together yield full alpha.
-        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
-        final int halfAlpha = (Color.alpha(color) / 2) << 24;
         final Paint p = getRipplePaint();
 
-        if (mMaskColorFilter != null) {
-            // The ripple timing depends on the paint's alpha value, so we need
-            // to push just the alpha channel into the paint and let the filter
-            // handle the full-alpha color.
-            final int fullAlphaColor = color | (0xFF << 24);
-            mMaskColorFilter.setColor(fullAlphaColor);
-
-            p.setColor(halfAlpha);
-            p.setColorFilter(mMaskColorFilter);
-            p.setShader(mMaskShader);
-        } else {
-            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
-            p.setColor(halfAlphaColor);
-            p.setColorFilter(null);
-            p.setShader(null);
-        }
-
         if (background != null && background.isVisible()) {
             background.draw(canvas, p);
         }
@@ -912,13 +852,49 @@
         mMask.draw(canvas);
     }
 
-    private Paint getRipplePaint() {
+    Paint getRipplePaint() {
         if (mRipplePaint == null) {
             mRipplePaint = new Paint();
             mRipplePaint.setAntiAlias(true);
             mRipplePaint.setStyle(Paint.Style.FILL);
         }
-        return mRipplePaint;
+
+        final float x = mHotspotBounds.exactCenterX();
+        final float y = mHotspotBounds.exactCenterY();
+
+        updateMaskShaderIfNeeded();
+
+        // Position the shader to account for canvas translation.
+        if (mMaskShader != null) {
+            final Rect bounds = getBounds();
+            mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
+            mMaskShader.setLocalMatrix(mMaskMatrix);
+        }
+
+        // Grab the color for the current state and cut the alpha channel in
+        // half so that the ripple and background together yield full alpha.
+        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+        final int halfAlpha = (Color.alpha(color) / 2) << 24;
+        final Paint p = mRipplePaint;
+
+        if (mMaskColorFilter != null) {
+            // The ripple timing depends on the paint's alpha value, so we need
+            // to push just the alpha channel into the paint and let the filter
+            // handle the full-alpha color.
+            final int fullAlphaColor = color | (0xFF << 24);
+            mMaskColorFilter.setColor(fullAlphaColor);
+
+            p.setColor(halfAlpha);
+            p.setColorFilter(mMaskColorFilter);
+            p.setShader(mMaskShader);
+        } else {
+            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+            p.setColor(halfAlphaColor);
+            p.setColorFilter(null);
+            p.setShader(null);
+        }
+
+        return p;
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index a675eaf..0b5020c 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -18,7 +18,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
@@ -29,8 +28,11 @@
 import android.util.MathUtils;
 import android.view.DisplayListCanvas;
 import android.view.RenderNodeAnimator;
+import android.view.animation.AnimationUtils;
 import android.view.animation.LinearInterpolator;
 
+import java.util.ArrayList;
+
 /**
  * Draws a ripple foreground.
  */
@@ -40,7 +42,7 @@
             400f, 1.4f, 0);
 
     // Pixel-based accelerations and velocities.
-    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
+    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 2048;
     private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
 
     // Bounded ripple animation properties.
@@ -49,8 +51,9 @@
     private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
     private static final float MAX_BOUNDED_RADIUS = 350;
 
-    private static final int RIPPLE_ENTER_DELAY = 80;
-    private static final int OPACITY_ENTER_DURATION_FAST = 120;
+    private static final int OPACITY_ENTER_DURATION = 75;
+    private static final int OPACITY_EXIT_DURATION = 150;
+    private static final int OPACITY_HOLD_DURATION = OPACITY_ENTER_DURATION + 150;
 
     // Parent-relative values for starting position.
     private float mStartingX;
@@ -72,7 +75,7 @@
     private float mBoundedRadius = 0;
 
     // Software rendering properties.
-    private float mOpacity = 1;
+    private float mOpacity = 0;
 
     // Values used to tween between the start and end positions.
     private float mTweenRadius = 0;
@@ -82,6 +85,22 @@
     /** Whether this ripple has finished its exit animation. */
     private boolean mHasFinishedExit;
 
+    /** Whether we can use hardware acceleration for the exit animation. */
+    private boolean mUsingProperties;
+
+    private long mEnterStartedAtMillis;
+
+    private ArrayList<RenderNodeAnimator> mPendingHwAnimators = new ArrayList<>();
+    private ArrayList<RenderNodeAnimator> mRunningHwAnimators = new ArrayList<>();
+
+    private ArrayList<Animator> mRunningSwAnimators = new ArrayList<>();
+
+    /**
+     * If set, force all ripple animations to not run on RenderThread, even if it would be
+     * available.
+     */
+    private final boolean mForceSoftware;
+
     /**
      * If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
      */
@@ -89,8 +108,9 @@
 
     public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
             boolean isBounded, boolean forceSoftware) {
-        super(owner, bounds, forceSoftware);
+        super(owner, bounds);
 
+        mForceSoftware = forceSoftware;
         mStartingX = startingX;
         mStartingY = startingY;
 
@@ -109,10 +129,7 @@
         clampStartingPosition();
     }
 
-    @Override
-    protected boolean drawSoftware(Canvas c, Paint p) {
-        boolean hasContent = false;
-
+    private void drawSoftware(Canvas c, Paint p) {
         final int origAlpha = p.getAlpha();
         final int alpha = (int) (origAlpha * mOpacity + 0.5f);
         final float radius = getCurrentRadius();
@@ -122,16 +139,51 @@
             p.setAlpha(alpha);
             c.drawCircle(x, y, radius, p);
             p.setAlpha(origAlpha);
-            hasContent = true;
         }
-
-        return hasContent;
     }
 
-    @Override
-    protected boolean drawHardware(DisplayListCanvas c) {
-        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
-        return true;
+    private void startPending(DisplayListCanvas c) {
+        if (!mPendingHwAnimators.isEmpty()) {
+            for (int i = 0; i < mPendingHwAnimators.size(); i++) {
+                RenderNodeAnimator animator = mPendingHwAnimators.get(i);
+                animator.setTarget(c);
+                animator.start();
+                mRunningHwAnimators.add(animator);
+            }
+            mPendingHwAnimators.clear();
+        }
+    }
+
+    private void pruneHwFinished() {
+        if (!mRunningHwAnimators.isEmpty()) {
+            for (int i = mRunningHwAnimators.size() - 1; i >= 0; i--) {
+                if (!mRunningHwAnimators.get(i).isRunning()) {
+                    mRunningHwAnimators.remove(i);
+                }
+            }
+        }
+    }
+
+    private void pruneSwFinished() {
+        if (!mRunningSwAnimators.isEmpty()) {
+            for (int i = mRunningSwAnimators.size() - 1; i >= 0; i--) {
+                if (!mRunningSwAnimators.get(i).isRunning()) {
+                    mRunningSwAnimators.remove(i);
+                }
+            }
+        }
+    }
+
+    private void drawHardware(DisplayListCanvas c, Paint p) {
+        startPending(c);
+        pruneHwFinished();
+        if (mPropPaint != null) {
+            mUsingProperties = true;
+            c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+        } else {
+            mUsingProperties = false;
+            drawSoftware(c, p);
+        }
     }
 
     /**
@@ -162,31 +214,115 @@
         return mHasFinishedExit;
     }
 
-    @Override
-    protected Animator createSoftwareEnter(boolean fast) {
+    private long computeFadeOutDelay() {
+        long timeSinceEnter = AnimationUtils.currentAnimationTimeMillis() - mEnterStartedAtMillis;
+        if (timeSinceEnter > 0 && timeSinceEnter < OPACITY_HOLD_DURATION) {
+            return OPACITY_HOLD_DURATION - timeSinceEnter;
+        }
+        return 0;
+    }
+
+    private void startSoftwareEnter() {
+        for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+            mRunningSwAnimators.get(i).cancel();
+        }
+        mRunningSwAnimators.clear();
+
         final int duration = getRadiusDuration();
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
-        tweenRadius.setAutoCancel(true);
         tweenRadius.setDuration(duration);
         tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
-        tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
+        tweenRadius.start();
+        mRunningSwAnimators.add(tweenRadius);
 
         final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
-        tweenOrigin.setAutoCancel(true);
         tweenOrigin.setDuration(duration);
         tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
-        tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
+        tweenOrigin.start();
+        mRunningSwAnimators.add(tweenOrigin);
 
         final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
+        opacity.setDuration(OPACITY_ENTER_DURATION);
         opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.start();
+        mRunningSwAnimators.add(opacity);
+    }
 
-        final AnimatorSet set = new AnimatorSet();
-        set.play(tweenOrigin).with(tweenRadius).with(opacity);
+    private void startSoftwareExit() {
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
+        opacity.setDuration(OPACITY_EXIT_DURATION);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.addListener(mAnimationListener);
+        opacity.setStartDelay(computeFadeOutDelay());
+        opacity.start();
+        mRunningSwAnimators.add(opacity);
+    }
 
-        return set;
+    private void startHardwareEnter() {
+        if (mForceSoftware) { return; }
+        mPropX = CanvasProperty.createFloat(getCurrentX());
+        mPropY = CanvasProperty.createFloat(getCurrentY());
+        mPropRadius = CanvasProperty.createFloat(getCurrentRadius());
+        final Paint paint = mOwner.getRipplePaint();
+        mPropPaint = CanvasProperty.createPaint(paint);
+
+        final int radiusDuration = getRadiusDuration();
+
+        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
+        radius.setDuration(radiusDuration);
+        radius.setInterpolator(DECELERATE_INTERPOLATOR);
+        mPendingHwAnimators.add(radius);
+
+        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
+        x.setDuration(radiusDuration);
+        x.setInterpolator(DECELERATE_INTERPOLATOR);
+        mPendingHwAnimators.add(x);
+
+        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
+        y.setDuration(radiusDuration);
+        y.setInterpolator(DECELERATE_INTERPOLATOR);
+        mPendingHwAnimators.add(y);
+
+        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+                RenderNodeAnimator.PAINT_ALPHA, paint.getAlpha());
+        opacity.setDuration(OPACITY_ENTER_DURATION);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.setStartValue(0);
+        mPendingHwAnimators.add(opacity);
+
+        invalidateSelf();
+    }
+
+    private void startHardwareExit() {
+        // Only run a hardware exit if we had a hardware enter to continue from
+        if (mForceSoftware || mPropPaint == null) return;
+
+        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+                RenderNodeAnimator.PAINT_ALPHA, 0);
+        opacity.setDuration(OPACITY_EXIT_DURATION);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.addListener(mAnimationListener);
+        opacity.setStartDelay(computeFadeOutDelay());
+        mPendingHwAnimators.add(opacity);
+        invalidateSelf();
+    }
+
+    /**
+     * Starts a ripple enter animation.
+     */
+    public final void enter() {
+        mEnterStartedAtMillis = AnimationUtils.currentAnimationTimeMillis();
+        startSoftwareEnter();
+        startHardwareEnter();
+    }
+
+    /**
+     * Starts a ripple exit animation.
+     */
+    public final void exit() {
+        startSoftwareExit();
+        startHardwareExit();
     }
 
     private float getCurrentX() {
@@ -207,96 +343,23 @@
         return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
     }
 
-    private int getOpacityExitDuration() {
-        return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
-    }
+    /**
+     * Draws the ripple to the canvas, inheriting the paint's color and alpha
+     * properties.
+     *
+     * @param c the canvas to which the ripple should be drawn
+     * @param p the paint used to draw the ripple
+     */
+    public void draw(Canvas c, Paint p) {
+        final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas;
 
-    @Override
-    protected Animator createSoftwareExit() {
-        final int radiusDuration;
-        final int originDuration;
-        final int opacityDuration;
-
-        radiusDuration = getRadiusDuration();
-        originDuration = radiusDuration;
-        opacityDuration = getOpacityExitDuration();
-
-        final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
-        tweenRadius.setAutoCancel(true);
-        tweenRadius.setDuration(radiusDuration);
-        tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
-        tweenOrigin.setAutoCancel(true);
-        tweenOrigin.setDuration(originDuration);
-        tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(opacityDuration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
-        final AnimatorSet set = new AnimatorSet();
-        set.play(tweenOrigin).with(tweenRadius).with(opacity);
-        set.addListener(mAnimationListener);
-
-        return set;
-    }
-
-    @Override
-    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
-        final int radiusDuration;
-        final int originDuration;
-        final int opacityDuration;
-
-        radiusDuration = getRadiusDuration();
-        originDuration = radiusDuration;
-        opacityDuration = getOpacityExitDuration();
-
-        final float startX = getCurrentX();
-        final float startY = getCurrentY();
-        final float startRadius = getCurrentRadius();
-
-        p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
-
-        mPropPaint = CanvasProperty.createPaint(p);
-        mPropRadius = CanvasProperty.createFloat(startRadius);
-        mPropX = CanvasProperty.createFloat(startX);
-        mPropY = CanvasProperty.createFloat(startY);
-
-        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
-        radius.setDuration(radiusDuration);
-        radius.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
-        x.setDuration(originDuration);
-        x.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
-        y.setDuration(originDuration);
-        y.setInterpolator(DECELERATE_INTERPOLATOR);
-
-        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
-                RenderNodeAnimator.PAINT_ALPHA, 0);
-        opacity.setDuration(opacityDuration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
-        opacity.addListener(mAnimationListener);
-
-        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-        set.add(radius);
-        set.add(opacity);
-        set.add(x);
-        set.add(y);
-
-        return set;
-    }
-
-    @Override
-    protected void jumpValuesToExit() {
-        mOpacity = 0;
-        mTweenX = 1;
-        mTweenY = 1;
-        mTweenRadius = 1;
+        pruneSwFinished();
+        if (hasDisplayListCanvas) {
+            final DisplayListCanvas hw = (DisplayListCanvas) c;
+            drawHardware(hw, p);
+        } else {
+            drawSoftware(c, p);
+        }
     }
 
     /**
@@ -319,10 +382,39 @@
         }
     }
 
+    /**
+     * Ends all animations, jumping values to the end state.
+     */
+    public void end() {
+        for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+            mRunningSwAnimators.get(i).end();
+        }
+        mRunningSwAnimators.clear();
+        for (int i = 0; i < mRunningHwAnimators.size(); i++) {
+            mRunningHwAnimators.get(i).end();
+        }
+        mRunningHwAnimators.clear();
+    }
+
+    private void onAnimationPropertyChanged() {
+        if (!mUsingProperties) {
+            invalidateSelf();
+        }
+    }
+
     private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animator) {
             mHasFinishedExit = true;
+            pruneHwFinished();
+            pruneSwFinished();
+
+            if (mRunningHwAnimators.isEmpty()) {
+                mPropPaint = null;
+                mPropRadius = null;
+                mPropX = null;
+                mPropY = null;
+            }
         }
     };
 
@@ -361,7 +453,7 @@
         @Override
         public void setValue(RippleForeground object, float value) {
             object.mTweenRadius = value;
-            object.invalidateSelf();
+            object.onAnimationPropertyChanged();
         }
 
         @Override
@@ -375,18 +467,18 @@
      */
     private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
             new FloatProperty<RippleForeground>("tweenOrigin") {
-                @Override
-                public void setValue(RippleForeground object, float value) {
-                    object.mTweenX = value;
-                    object.mTweenY = value;
-                    object.invalidateSelf();
-                }
+        @Override
+        public void setValue(RippleForeground object, float value) {
+            object.mTweenX = value;
+            object.mTweenY = value;
+            object.onAnimationPropertyChanged();
+        }
 
-                @Override
-                public Float get(RippleForeground object) {
-                    return object.mTweenX;
-                }
-            };
+        @Override
+        public Float get(RippleForeground object) {
+            return object.mTweenX;
+        }
+    };
 
     /**
      * Property for animating opacity between 0 and its target value.
@@ -396,7 +488,7 @@
         @Override
         public void setValue(RippleForeground object, float value) {
             object.mOpacity = value;
-            object.invalidateSelf();
+            object.onAnimationPropertyChanged();
         }
 
         @Override
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 3c8736e..0485625 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -148,11 +148,15 @@
     int count = android_atomic_dec(&gCount);
     if (kIsDebug) {
         ALOGI("Destroying AssetManager in %p #%d\n", this, count);
+    } else {
+        ALOGV("Destroying AssetManager in %p #%d\n", this, count);
     }
 
     // Manually close any fd paths for which we have not yet opened their zip (which
     // will take ownership of the fd and close it when done).
     for (size_t i=0; i<mAssetPaths.size(); i++) {
+        ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd,
+                mAssetPaths[i].zip.get());
         if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) {
             close(mAssetPaths[i].rawFd);
         }
@@ -202,7 +206,7 @@
          ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
 
     ap.isSystemAsset = isSystemAsset;
-    mAssetPaths.add(ap);
+    ssize_t apPos = mAssetPaths.add(ap);
 
     // new paths are always added at the end
     if (cookie) {
@@ -219,7 +223,7 @@
 #endif
 
     if (mResources != NULL) {
-        appendPathToResTable(ap, appAsLib);
+        appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
     }
 
     return true;
@@ -304,7 +308,7 @@
 
     ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string());
 
-    mAssetPaths.add(ap);
+    ssize_t apPos = mAssetPaths.add(ap);
 
     // new paths are always added at the end
     if (cookie) {
@@ -312,7 +316,7 @@
     }
 
     if (mResources != NULL) {
-        appendPathToResTable(ap, appAsLib);
+        appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
     }
 
     return true;
@@ -442,7 +446,8 @@
         i--;
         ALOGV("Looking for asset '%s' in '%s'\n",
                 assetName.string(), mAssetPaths.itemAt(i).path.string());
-        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode,
+                mAssetPaths.editItemAt(i));
         if (pAsset != NULL) {
             return pAsset != kExcludedAsset ? pAsset : NULL;
         }
@@ -471,7 +476,7 @@
         i--;
         ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
         Asset* pAsset = openNonAssetInPathLocked(
-            fileName, mode, mAssetPaths.itemAt(i));
+            fileName, mode, mAssetPaths.editItemAt(i));
         if (pAsset != NULL) {
             if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1);
             return pAsset != kExcludedAsset ? pAsset : NULL;
@@ -493,7 +498,7 @@
         ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
                 mAssetPaths.itemAt(which).path.string());
         Asset* pAsset = openNonAssetInPathLocked(
-            fileName, mode, mAssetPaths.itemAt(which));
+            fileName, mode, mAssetPaths.editItemAt(which));
         if (pAsset != NULL) {
             return pAsset != kExcludedAsset ? pAsset : NULL;
         }
@@ -527,7 +532,7 @@
     }
 }
 
-bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
+bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const {
     // skip those ap's that correspond to system overlays
     if (ap.isSystemOverlay) {
         return true;
@@ -645,7 +650,8 @@
     bool onlyEmptyResources = true;
     const size_t N = mAssetPaths.size();
     for (size_t i=0; i<N; i++) {
-        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
+        bool empty = appendPathToResTable(
+                const_cast<AssetManager*>(this)->mAssetPaths.editItemAt(i));
         onlyEmptyResources = onlyEmptyResources && empty;
     }
 
@@ -770,7 +776,7 @@
  * be used.
  */
 Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
-    const asset_path& ap)
+    asset_path& ap)
 {
     Asset* pAsset = NULL;
 
@@ -851,17 +857,19 @@
  * Return a pointer to one of our open Zip archives.  Returns NULL if no
  * matching Zip file exists.
  */
-ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
+ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap)
 {
-    ALOGV("getZipFileLocked() in %p\n", this);
+    ALOGV("getZipFileLocked() in %p: ap=%p zip=%p", this, &ap, ap.zip.get());
 
     if (ap.zip != NULL) {
         return ap.zip->getZip();
     }
 
     if (ap.rawFd < 0) {
+        ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string());
         ap.zip = mZipSet.getSharedZip(ap.path);
     } else {
+        ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd);
         ap.zip = SharedZip::create(ap.rawFd, ap.path);
 
     }
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index 4254614..ecc5dc1 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -222,16 +222,16 @@
         bool isSystemOverlay;
         bool isSystemAsset;
         bool assumeOwnership;
-        mutable sp<SharedZip> zip;
+        sp<SharedZip> zip;
     };
 
     Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
-        const asset_path& path);
+        asset_path& path);
     String8 createPathNameLocked(const asset_path& path, const char* rootDir);
     String8 createZipSourceNameLocked(const String8& zipFileName,
         const String8& dirName, const String8& fileName);
 
-    ZipFileRO* getZipFileLocked(const asset_path& path);
+    ZipFileRO* getZipFileLocked(asset_path& path);
     Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
     Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
         const ZipEntryRO entry, AccessMode mode, const String8& entryName);
@@ -247,7 +247,7 @@
     const ResTable* getResTable(bool required = true) const;
     void setLocaleLocked(const char* locale);
     void updateResourceParamsLocked() const;
-    bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const;
+    bool appendPathToResTable(asset_path& ap, bool appAsLib=false) const;
 
     Asset* openIdmapLocked(const struct asset_path& ap) const;
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 24ce1e4..5c577ae 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -2,9 +2,10 @@
     name: "hwui_defaults",
     defaults: [
         "hwui_static_deps",
-        "skia_deps"
+        "skia_deps",
         //"hwui_bugreport_font_cache_usage",
         //"hwui_compile_for_perf",
+        "hwui_pgo",
     ],
 
     cpp_std: "c++17",
@@ -109,6 +110,22 @@
     include_dirs: ["frameworks/native/opengl/libs/GLES2"],
 }
 
+// Build libhwui with PGO by default.
+// Location of PGO profile data is defined in build/soong/cc/pgo.go
+// and is separate from hwui.
+// To turn it off, set ANDROID_PGO_NO_PROFILE_USE environment variable
+// or set enable_profile_use property to false.
+cc_defaults {
+    name: "hwui_pgo",
+
+    pgo: {
+        instrumentation: true,
+        profile_file: "hwui/hwui.profdata",
+        benchmarks: ["hwui"],
+        enable_profile_use: false,
+    },
+}
+
 // ------------------------
 // library
 // ------------------------
@@ -255,18 +272,6 @@
         // Has moderate overhead
         "hwui_enable_opengl_validation",
     ],
-
-    // Build libhwui with PGO by default.
-    // Location of PGO profile data is defined in build/soong/cc/pgo.go
-    // and is separate from hwui.
-    // To turn it off, set ANDROID_PGO_NO_PROFILE_USE environment variable
-    // or set enable_profile_use property to false.
-    pgo: {
-        instrumentation: true,
-        profile_file: "hwui/hwui.profdata",
-        benchmarks: ["hwui"],
-        enable_profile_use: false,
-    },
 }
 
 // ------------------------
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 90fe0b7..bad766c 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -29,12 +29,6 @@
 minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
                                                         const Typeface* typeface) {
     const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
-    minikin::FontStyle resolved = resolvedFace->fStyle;
-
-    const minikin::FontVariant minikinVariant =
-            (paint->getFontVariant() == minikin::FontVariant::ELEGANT)
-                    ? minikin::FontVariant::ELEGANT
-                    : minikin::FontVariant::COMPACT;
 
     minikin::MinikinPaint minikinPaint;
     /* Prepare minikin Paint */
@@ -46,7 +40,8 @@
     minikinPaint.wordSpacing = paint->getWordSpacing();
     minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
-    minikinPaint.fontStyle = minikin::FontStyle(minikinVariant, resolved.weight, resolved.slant);
+    minikinPaint.familyVariant = paint->getFamilyVariant();
+    minikinPaint.fontStyle = resolvedFace->fStyle;
     minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
     minikinPaint.hyphenEdit = minikin::HyphenEdit(paint->getHyphenEdit());
     return minikinPaint;
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 76beb11..002f759 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -73,9 +73,9 @@
 
     uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
 
-    void setFontVariant(minikin::FontVariant variant) { mFontVariant = variant; }
+    void setFamilyVariant(minikin::FontFamily::Variant variant) { mFamilyVariant = variant; }
 
-    minikin::FontVariant getFontVariant() const { return mFontVariant; }
+    minikin::FontFamily::Variant getFamilyVariant() const { return mFamilyVariant; }
 
     void setHyphenEdit(uint32_t hyphen) { mHyphenEdit = hyphen; }
 
@@ -90,7 +90,7 @@
     float mWordSpacing = 0;
     std::string mFontFeatureSettings;
     uint32_t mMinikinLocaleListId;
-    minikin::FontVariant mFontVariant;
+    minikin::FontFamily::Variant mFamilyVariant;
     uint32_t mHyphenEdit = 0;
     // The native Typeface object has the same lifetime of the Java Typeface
     // object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 94492c5..ae9c475 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,7 +24,7 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFontVariant(minikin::FontVariant::DEFAULT) {}
+        , mFamilyVariant(minikin::FontFamily::Variant::DEFAULT) {}
 
 Paint::Paint(const Paint& paint)
         : SkPaint(paint)
@@ -32,7 +32,7 @@
         , mWordSpacing(paint.mWordSpacing)
         , mFontFeatureSettings(paint.mFontFeatureSettings)
         , mMinikinLocaleListId(paint.mMinikinLocaleListId)
-        , mFontVariant(paint.mFontVariant)
+        , mFamilyVariant(paint.mFamilyVariant)
         , mHyphenEdit(paint.mHyphenEdit)
         , mTypeface(paint.mTypeface) {}
 
@@ -42,7 +42,7 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFontVariant(minikin::FontVariant::DEFAULT) {}
+        , mFamilyVariant(minikin::FontFamily::Variant::DEFAULT) {}
 
 Paint::~Paint() {}
 
@@ -52,7 +52,7 @@
     mWordSpacing = other.mWordSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
     mMinikinLocaleListId = other.mMinikinLocaleListId;
-    mFontVariant = other.mFontVariant;
+    mFamilyVariant = other.mFamilyVariant;
     mHyphenEdit = other.mHyphenEdit;
     mTypeface = other.mTypeface;
     return *this;
@@ -62,7 +62,8 @@
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
            a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
            a.mFontFeatureSettings == b.mFontFeatureSettings &&
-           a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFontVariant == b.mFontVariant &&
-           a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface;
+           a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
+           a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
+           a.mTypeface == b.mTypeface;
 }
 }  // namespace android
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index e527adc..ebc14c8 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -45,7 +45,7 @@
 
 static minikin::FontStyle computeMinikinStyle(int weight, bool italic) {
     return minikin::FontStyle(uirenderer::MathUtils::clamp(weight, 1, 1000),
-                              static_cast<minikin::FontSlant>(italic));
+                              static_cast<minikin::FontStyle::Slant>(italic));
 }
 
 // Resolve the relative weight from the baseWeight and target style.
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 1fcc028..66d6f52 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -81,39 +81,39 @@
 
 TEST(TypefaceTest, createWithDifferentBaseWeight) {
     std::unique_ptr<Typeface> bold(Typeface::createWithDifferentBaseWeight(nullptr, 700));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, bold->fAPIStyle);
 
     std::unique_ptr<Typeface> light(Typeface::createWithDifferentBaseWeight(nullptr, 300));
-    EXPECT_EQ(300, light->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, light->fStyle.slant);
+    EXPECT_EQ(300, light->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, light->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, light->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromRegular) {
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, Typeface::kNormal));
-    EXPECT_EQ(400, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
+    EXPECT_EQ(400, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, Typeface::kBold));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, Typeface::kItalic));
-    EXPECT_EQ(400, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(Typeface::createRelative(nullptr, Typeface::kBoldItalic));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -123,30 +123,30 @@
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
     // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(700, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
+    EXPECT_EQ(700, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
     // Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(1000, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(1000, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
     // Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(700, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(700, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"),
     // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(1000, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(1000, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -156,30 +156,30 @@
     // In Java, Typeface.create(Typeface.create("sans-serif-light"),
     // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(300, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
+    EXPECT_EQ(300, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"),
     // Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(600, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(600, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"),
     // Typeface.ITLIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(300, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(300, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"),
     // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(600, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(600, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -189,22 +189,22 @@
     // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
     // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(400, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
+    EXPECT_EQ(400, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
     // Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD),
     // Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(400, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -212,8 +212,8 @@
     // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -224,23 +224,23 @@
     // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
     // Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(400, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
+    EXPECT_EQ(400, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT,
     // Typeface.ITALIC), Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
     // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC),
     // Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(400, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -248,8 +248,8 @@
     // Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -261,8 +261,8 @@
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.NORMAL);
     std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
-    EXPECT_EQ(400, normal->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, normal->fStyle.slant);
+    EXPECT_EQ(400, normal->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, normal->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java,
@@ -270,8 +270,8 @@
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.BOLD);
     std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
@@ -279,8 +279,8 @@
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.ITALIC);
     std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
-    EXPECT_EQ(400, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -289,8 +289,8 @@
     // Typeface.create(typeface, Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createRelative(base.get(), Typeface::kBoldItalic));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
@@ -300,8 +300,8 @@
     // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(false)
     //     .build();
     std::unique_ptr<Typeface> regular(Typeface::createAbsolute(nullptr, 400, false));
-    EXPECT_EQ(400, regular->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, regular->fStyle.slant);
+    EXPECT_EQ(400, regular->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java,
@@ -309,8 +309,8 @@
     // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
     //     .build();
     std::unique_ptr<Typeface> bold(Typeface::createAbsolute(nullptr, 700, false));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
@@ -318,8 +318,8 @@
     // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
     //     .build();
     std::unique_ptr<Typeface> italic(Typeface::createAbsolute(nullptr, 400, true));
-    EXPECT_EQ(400, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -327,8 +327,8 @@
     // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
     //     .build();
     std::unique_ptr<Typeface> boldItalic(Typeface::createAbsolute(nullptr, 700, true));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 
     // In Java,
@@ -336,8 +336,8 @@
     // Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
     //     .build();
     std::unique_ptr<Typeface> over1000(Typeface::createAbsolute(nullptr, 1100, false));
-    EXPECT_EQ(1000, over1000->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, over1000->fStyle.slant);
+    EXPECT_EQ(1000, over1000->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
@@ -346,24 +346,24 @@
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
     std::unique_ptr<Typeface> regular(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular), 400, false));
-    EXPECT_EQ(400, regular->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, regular->fStyle.slant);
+    EXPECT_EQ(400, regular->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new
     // Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
     std::unique_ptr<Typeface> bold(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 700, false));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new
     // Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
     std::unique_ptr<Typeface> italic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic), 400, true));
-    EXPECT_EQ(400, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -371,8 +371,8 @@
     // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic), 700, true));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
@@ -380,8 +380,8 @@
     // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
     std::unique_ptr<Typeface> over1000(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 1100, false));
-    EXPECT_EQ(1000, over1000->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, over1000->fStyle.slant);
+    EXPECT_EQ(1000, over1000->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
@@ -389,30 +389,30 @@
     // In Java, new Typeface.Builder("Roboto-Regular.ttf").build();
     std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
             makeSingleFamlyVector(kRobotoRegular), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(400, regular->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, regular->fStyle.slant);
+    EXPECT_EQ(400, regular->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Bold.ttf").build();
     std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
             makeSingleFamlyVector(kRobotoBold), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(700, bold->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, bold->fStyle.slant);
+    EXPECT_EQ(700, bold->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Italic.ttf").build();
     std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
             makeSingleFamlyVector(kRobotoItalic), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(400, italic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, italic->fStyle.slant);
+    EXPECT_EQ(400, italic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-BoldItalic.ttf").build();
     std::unique_ptr<Typeface> boldItalic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic),
                                          RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(700, boldItalic->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::ITALIC, boldItalic->fStyle.slant);
+    EXPECT_EQ(700, boldItalic->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 }
 
@@ -422,8 +422,8 @@
             buildFamily(kRobotoBoldItalic)};
     std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
             std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(400, typeface->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, typeface->fStyle.slant);
+    EXPECT_EQ(400, typeface->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
 }
 
 TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
@@ -431,8 +431,8 @@
             buildFamily(kRobotoBold), buildFamily(kRobotoItalic), buildFamily(kRobotoBoldItalic)};
     std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
             std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
-    EXPECT_EQ(700, typeface->fStyle.weight);
-    EXPECT_EQ(minikin::FontSlant::UPRIGHT, typeface->fStyle.slant);
+    EXPECT_EQ(700, typeface->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
 }
 
 }  // namespace
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index ce41849..faea9b2 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -110,6 +110,12 @@
     void end(long long token);
 
     /**
+     * Returns how many bytes are buffered in ProtoOutputStream.
+     * Notice, this is not the actual(compact) size of the output data.
+     */
+    size_t bytesWritten();
+
+    /**
      * 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.
      */
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index 9d8ee72..1904d40 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -295,6 +295,12 @@
     }
 }
 
+size_t
+ProtoOutputStream::bytesWritten()
+{
+    return mBuffer.size();
+}
+
 bool
 ProtoOutputStream::compact() {
     if (mCompact) return true;
diff --git a/location/Android.mk b/location/Android.mk
index feeb8ce..50509c6 100644
--- a/location/Android.mk
+++ b/location/Android.mk
@@ -14,4 +14,4 @@
 
 LOCAL_PATH := $(call my-dir)
 
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(call all-subdir-makefiles, $(LOCAL_PATH))
\ No newline at end of file
diff --git a/location/tests/locationtests/Android.mk b/location/tests/locationtests/Android.mk
index 902cd96..73b2bb5 100644
--- a/location/tests/locationtests/Android.mk
+++ b/location/tests/locationtests/Android.mk
@@ -10,5 +10,13 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksLocationTests
 
-include $(BUILD_PACKAGE)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    core-test-rules \
+    guava \
+    mockito-target-minus-junit4 \
+    frameworks-base-testutils \
+    truth-prebuilt \
 
+LOCAL_COMPATIBILITY_SUITE := device-tests
+include $(BUILD_PACKAGE)
diff --git a/location/tests/locationtests/AndroidManifest.xml b/location/tests/locationtests/AndroidManifest.xml
index 1d9df0f..ddb8ea6 100644
--- a/location/tests/locationtests/AndroidManifest.xml
+++ b/location/tests/locationtests/AndroidManifest.xml
@@ -4,9 +4,9 @@
      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.
@@ -23,13 +23,13 @@
     <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-    
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation
-    	android:name="android.test.InstrumentationTestRunner"
-    	android:targetPackage="com.android.frameworks.locationtests"
-    	android:label="Frameworks Location Tests" />
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.locationtests"
+        android:label="Frameworks Location Tests" />
 </manifest>
diff --git a/location/tests/locationtests/AndroidTest.xml b/location/tests/locationtests/AndroidTest.xml
new file mode 100644
index 0000000..0c5b7cc
--- /dev/null
+++ b/location/tests/locationtests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Frameworks Location API Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksLocationTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="FrameworksLocationTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.locationtests" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/location/tests/locationtests/src/android/location/GnssStatusTest.java b/location/tests/locationtests/src/android/location/GnssStatusTest.java
new file mode 100644
index 0000000..79ea0d6
--- /dev/null
+++ b/location/tests/locationtests/src/android/location/GnssStatusTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link GnssStatus}.
+ */
+@SmallTest
+public class GnssStatusTest extends TestCase {
+
+  private static final String TAG = GnssStatusTest.class.getSimpleName();
+  public void setUp() throws Exception {
+    super.setUp();
+  }
+
+  /*
+   * Create {@link GnssStatus} with default value, verify whether its fields are set correctly.
+   *
+   */
+  public void testEmptyGnssStatus() throws Exception {
+    Log.i(TAG, "testEmptyGnssStatus");
+    List<SatelliteInfo> svInfos = new ArrayList<>();
+    GnssStatus gnssStatus = createGnssStatus(svInfos);
+    verifyGnssStatus(svInfos, gnssStatus);
+  }
+
+  /*
+   * Create {@link GnssStatus} with only one satellite info, verify whether its fields are set
+   * correctly.
+   */
+  public void testOneSatelliteGnssStatus() throws Exception {
+    Log.i(TAG, "testOneSatelliteGnssStatus");
+    List<SatelliteInfo> svInfos = new ArrayList<>();
+    SatelliteInfo svInfo =
+        new SatelliteInfo(100,1, true, true, true, true, 100f, 20.3f, 45.5f, 100.23f);
+    svInfos.add(svInfo);
+    GnssStatus gnssStatus = createGnssStatus(svInfos);
+    verifyGnssStatus(svInfos, gnssStatus);
+  }
+
+  /*
+   * Create {@link GnssStatus} with multiple satellite info, verify whether its fields are set
+   * correctly.
+   */
+  public void testMultipleSatellitesGnssStatus() throws Exception {
+    Log.i(TAG, "testMultipleSatellitesGnssStatus");
+    List<SatelliteInfo> svInfos = new ArrayList<>();
+    SatelliteInfo svInfo1 =
+        new SatelliteInfo(20, 1,true, true, true, true, 10.1f, 20.3f, 45.5f, 111.23f);
+    SatelliteInfo svInfo2 =
+        new SatelliteInfo(50, 2, true, false, true, false, 20.2f, 21.3f, 46.5f, 222.23f);
+    SatelliteInfo svInfo3 =
+        new SatelliteInfo(192, 3, false, true, false, true, 30.3f, 22.3f, 47.5f, 333.23f);
+    SatelliteInfo svInfo4 =
+        new SatelliteInfo(250, 4, false, false, false, false, 40.4f, 23.3f, 48.5f, 444.23f);
+    svInfos.add(svInfo1);
+    svInfos.add(svInfo2);
+    svInfos.add(svInfo3);
+    svInfos.add(svInfo4);
+    GnssStatus gnssStatus = createGnssStatus(svInfos);
+    verifyGnssStatus(svInfos, gnssStatus);
+  }
+
+  private void verifyGnssStatus(List<SatelliteInfo> svInfos, GnssStatus gnssStatus) {
+    Log.i(TAG, String.format("Verifing {0} satellites info.",svInfos.size()));
+    assertEquals(TAG + "::SatelliteCount", svInfos.size(),
+        gnssStatus.getSatelliteCount());
+    for (int i = 0; i< svInfos.size(); i++) {
+      SatelliteInfo svInfo = svInfos.get(i);
+      assertEquals(TAG + "::Svid", svInfo.mSvid, gnssStatus.getSvid(i));
+      assertEquals(TAG + "::ConstellationType", svInfo.mConstellationType,
+          gnssStatus.getConstellationType(i));
+      assertEquals(TAG + "::Cn0DbHz", svInfo.mCn0DbHz, gnssStatus.getCn0DbHz(i));
+      assertEquals(TAG + "::Elevation", svInfo.mElevation,
+          gnssStatus.getElevationDegrees(i));
+      assertEquals(TAG + "::Azimuth", svInfo.mAzimuth, gnssStatus.getAzimuthDegrees(i));
+      assertEquals(TAG + "::CarrierFrequencyHz", svInfo.mCarrierFrequency,
+          gnssStatus.getCarrierFrequencyHz(i));
+      assertEquals(TAG + "::hasEphemerisData", svInfo.mHasEphemris,
+          gnssStatus.hasEphemerisData(i));
+      assertEquals(TAG + "::HasAlmanacData", svInfo.mHasAlmanac,
+          gnssStatus.hasAlmanacData(i));
+      assertEquals(TAG + "::UsedInFix", svInfo.mUsedInFix, gnssStatus.usedInFix(i));
+      assertEquals(TAG + "::HasCarrierFrequencyHz", svInfo.mHasCarriesFrequency,
+          gnssStatus.hasCarrierFrequencyHz(i));
+    }
+  }
+
+  private static GnssStatus createGnssStatus(List<SatelliteInfo> svInfos) throws Exception {
+    Class<?> intClass = Integer.TYPE;
+    Class<?> floatArrayClass = Class.forName("[F");
+    Class<?> intArrayClass = Class.forName("[I");
+    Class[] cArg = new Class[6];
+    cArg[0] = intClass;
+    cArg[1] = intArrayClass;
+    cArg[2] = floatArrayClass;
+    cArg[3] = floatArrayClass;
+    cArg[4] = floatArrayClass;
+    cArg[5] = floatArrayClass;
+    Constructor<GnssStatus>  ctor = GnssStatus.class.getDeclaredConstructor(cArg);
+    ctor.setAccessible(true);
+    return ctor.newInstance(svInfos.size(),
+        SatelliteInfo.getSvidWithFlagsArray(svInfos),
+        SatelliteInfo.getCn0sArray(svInfos),
+        SatelliteInfo.getElevationsArray(svInfos),
+        SatelliteInfo.getAzimuthsArray(svInfos),
+        SatelliteInfo.getCarrierFrequencyArray(svInfos));
+  }
+}
diff --git a/location/tests/locationtests/src/android/location/GpsStatusTest.java b/location/tests/locationtests/src/android/location/GpsStatusTest.java
deleted file mode 100644
index 316e88d..0000000
--- a/location/tests/locationtests/src/android/location/GpsStatusTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2015 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.location;
-
-import junit.framework.TestCase;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * Unit tests for {@link GpsStatus}.
- */
-@SmallTest
-public class GpsStatusTest extends TestCase {
-
-    private static final int MAX_VALUE = 250;
-
-    private final Random mRandom = new Random();
-
-    private GpsStatus mStatus;
-    private int mCount;
-    private int[] mPrns;
-    private float[] mCn0s;
-    private float[] mElevations;
-    private float[] mAzimuth;
-    private int mEphemerisMask;
-    private int mAlmanacMask;
-    private int mUsedInFixMask;
-
-    public void setUp() throws Exception {
-        super.setUp();
-        mStatus = createGpsStatus();
-        generateSatellitesData(generateInt());
-    }
-
-    public void testEmptyGpsStatus() throws Exception {
-        verifyIsEmpty(mStatus);
-    }
-
-    public void testGpsStatusIterator() throws Exception {
-        generateSatellitesData(2);
-        setSatellites(mStatus);
-        Iterator<GpsSatellite> iterator = mStatus.getSatellites().iterator();
-        assertTrue("hasNext(1)", iterator.hasNext());
-        assertTrue("hasNext(1) does not overflow", iterator.hasNext());
-        GpsSatellite satellite1 = iterator.next();
-        assertNotNull("satellite", satellite1);
-        assertTrue("hasNext(2)", iterator.hasNext());
-        assertTrue("hasNext(2) does not overflow", iterator.hasNext());
-        GpsSatellite satellite2 = iterator.next();
-        assertNotNull("satellite", satellite2);
-        assertFalse("hasNext() no elements", iterator.hasNext());
-    }
-
-    public void testTtff() throws Exception {
-        int testTtff = generateInt();
-        set(mStatus, testTtff);
-        verifyTtff(mStatus, testTtff);
-    }
-
-    public void testCopyTtff() throws Exception {
-        int testTtff = generateInt();
-        verifyTtff(mStatus, 0);
-
-        GpsStatus otherStatus = createGpsStatus();
-        set(otherStatus, testTtff);
-        verifyTtff(otherStatus, testTtff);
-
-        set(mStatus, otherStatus);
-        verifyTtff(mStatus, testTtff);
-    }
-
-    public void testSetSatellites() throws Exception {
-        setSatellites(mStatus);
-        verifySatellites(mStatus);
-    }
-
-    public void testCopySatellites() throws Exception {
-        verifyIsEmpty(mStatus);
-
-        GpsStatus otherStatus = createGpsStatus();
-        setSatellites(otherStatus);
-        verifySatellites(otherStatus);
-
-        set(mStatus, otherStatus);
-        verifySatellites(mStatus);
-    }
-
-    public void testOverrideSatellites() throws Exception {
-        setSatellites(mStatus);
-        verifySatellites(mStatus);
-
-        GpsStatus otherStatus = createGpsStatus();
-        generateSatellitesData(mCount, true /* reusePrns */);
-        setSatellites(otherStatus);
-        verifySatellites(otherStatus);
-
-        set(mStatus, otherStatus);
-        verifySatellites(mStatus);
-    }
-
-    public void testAddSatellites() throws Exception {
-        int count = 10;
-        generateSatellitesData(count);
-        setSatellites(mStatus);
-        verifySatellites(mStatus);
-
-        GpsStatus otherStatus = createGpsStatus();
-        generateSatellitesData(count);
-        setSatellites(otherStatus);
-        verifySatellites(otherStatus);
-
-        set(mStatus, otherStatus);
-        verifySatellites(mStatus);
-    }
-
-    public void testAddMoreSatellites() throws Exception {
-        int count = 25;
-        generateSatellitesData(count);
-        setSatellites(mStatus);
-        verifySatellites(mStatus);
-
-        GpsStatus otherStatus = createGpsStatus();
-        generateSatellitesData(count * 2);
-        setSatellites(otherStatus);
-        verifySatellites(otherStatus);
-
-        set(mStatus, otherStatus);
-        verifySatellites(mStatus);
-    }
-
-    public void testAddLessSatellites() throws Exception {
-        int count = 25;
-        generateSatellitesData(count * 2);
-        setSatellites(mStatus);
-        verifySatellites(mStatus);
-
-        GpsStatus otherStatus = createGpsStatus();
-        generateSatellitesData(count);
-        setSatellites(otherStatus);
-        verifySatellites(otherStatus);
-
-        set(mStatus, otherStatus);
-        verifySatellites(mStatus);
-    }
-
-    private static void verifyIsEmpty(GpsStatus status) {
-        verifySatelliteCount(status, 0);
-        verifyTtff(status, 0);
-    }
-
-    private static void verifySatelliteCount(GpsStatus status, int expectedCount) {
-        int satellites = 0;
-        for (GpsSatellite s : status.getSatellites()) {
-            ++satellites;
-        }
-        assertEquals("GpsStatus::SatelliteCount", expectedCount, satellites);
-    }
-
-    private void verifySatellites(GpsStatus status) {
-        verifySatelliteCount(status, mCount);
-        verifySatellites(status, mCount, mPrns, mCn0s, mElevations, mAzimuth, mEphemerisMask,
-                mAlmanacMask, mUsedInFixMask);
-    }
-
-    private static void verifySatellites(
-            GpsStatus status,
-            int count,
-            int[] prns,
-            float[] cn0s,
-            float[] elevations,
-            float[] azimuth,
-            int ephemerisMask,
-            int almanacMask,
-            int usedInFixMask) {
-        for (int i = 0; i < count; ++i) {
-            int prn = prns[i];
-            GpsSatellite satellite = getSatellite(status, prn);
-            assertNotNull(getSatelliteAssertInfo(i, prn, "non-null"), satellite);
-            assertEquals(getSatelliteAssertInfo(i, prn, "Snr"), cn0s[i], satellite.getSnr());
-            assertEquals(
-                    getSatelliteAssertInfo(i, prn, "Elevation"),
-                    elevations[i],
-                    satellite.getElevation());
-            assertEquals(
-                    getSatelliteAssertInfo(i, prn, "Azimuth"),
-                    azimuth[i],
-                    satellite.getAzimuth());
-            int prnShift = 1 << (prn - 1);
-            assertEquals(
-                    getSatelliteAssertInfo(i, prn, "ephemeris"),
-                    (ephemerisMask & prnShift) != 0,
-                    satellite.hasEphemeris());
-            assertEquals(
-                    getSatelliteAssertInfo(i, prn, "almanac"),
-                    (almanacMask & prnShift) != 0,
-                    satellite.hasAlmanac());
-            assertEquals(
-                    getSatelliteAssertInfo(i, prn, "usedInFix"),
-                    (usedInFixMask & prnShift) != 0,
-                    satellite.usedInFix());
-        }
-    }
-
-    private static void verifyTtff(GpsStatus status, int expectedTtff) {
-        assertEquals("GpsStatus::TTFF", expectedTtff, status.getTimeToFirstFix());
-    }
-
-    private static GpsStatus createGpsStatus() throws Exception {
-        Constructor<GpsStatus>  ctor = GpsStatus.class.getDeclaredConstructor();
-        ctor.setAccessible(true);
-        return ctor.newInstance();
-    }
-
-    private static void set(GpsStatus status, int ttff) throws Exception {
-        Class<?> statusClass = status.getClass();
-        Method setTtff = statusClass.getDeclaredMethod("setTimeToFirstFix", Integer.TYPE);
-        setTtff.setAccessible(true);
-        setTtff.invoke(status, ttff);
-    }
-
-    private static void set(GpsStatus status, GpsStatus statusToSet) throws Exception {
-        Class<?> statusClass = status.getClass();
-        Method setStatus = statusClass.getDeclaredMethod("setStatus", statusClass);
-        setStatus.setAccessible(true);
-        setStatus.invoke(status, statusToSet);
-    }
-
-    private void setSatellites(GpsStatus status) throws Exception {
-        set(status, mCount, mPrns, mCn0s, mElevations, mAzimuth, mEphemerisMask, mAlmanacMask,
-                mUsedInFixMask);
-    }
-
-    private static void set(
-            GpsStatus status,
-            int count,
-            int[] prns,
-            float[] cn0s,
-            float[] elevations,
-            float[] azimuth,
-            int ephemerisMask,
-            int almanacMask,
-            int usedInFixMask) throws Exception {
-        Class<?> statusClass = status.getClass();
-        Class<?> intClass = Integer.TYPE;
-        Class<?> floatArrayClass = Class.forName("[F");
-        Method setStatus = statusClass.getDeclaredMethod(
-                "setStatus",
-                intClass,
-                Class.forName("[I"),
-                floatArrayClass,
-                floatArrayClass,
-                floatArrayClass,
-                intClass,
-                intClass,
-                intClass);
-        setStatus.setAccessible(true);
-        setStatus.invoke(
-                status,
-                count,
-                prns,
-                cn0s,
-                elevations,
-                azimuth,
-                ephemerisMask,
-                almanacMask,
-                usedInFixMask);
-    }
-
-    private int generateInt() {
-        return mRandom.nextInt(MAX_VALUE) + 1;
-    }
-
-    private int[] generateIntArray(int count) {
-        Set<Integer> generatedPrns = new HashSet<>();
-        int[] array = new int[count];
-        for(int i = 0; i < count; ++i) {
-            int generated;
-            do {
-                generated = generateInt();
-            } while (generatedPrns.contains(generated));
-            array[i] = generated;
-            generatedPrns.add(generated);
-        }
-        return array;
-    }
-
-    private float[] generateFloatArray(int count) {
-        float[] array = new float[count];
-        for(int i = 0; i < count; ++i) {
-            array[i] = generateInt();
-        }
-        return array;
-    }
-
-    private int generateMask(int[] prns) {
-        int mask = 0;
-        int prnsLength = prns.length;
-        for (int i = 0; i < prnsLength; ++i) {
-            if (mRandom.nextBoolean()) {
-                mask |= 1 << (prns[i] - 1);
-            }
-        }
-        return mask;
-    }
-
-    private void generateSatellitesData(int count) {
-        generateSatellitesData(count, false /* reusePrns */);
-    }
-
-    private void generateSatellitesData(int count, boolean reusePrns) {
-        mCount = count;
-        if (!reusePrns) {
-            mPrns = generateIntArray(count);
-        }
-        mCn0s = generateFloatArray(count);
-        mElevations = generateFloatArray(count);
-        mAzimuth = generateFloatArray(count);
-        mEphemerisMask = generateMask(mPrns);
-        mAlmanacMask = generateMask(mPrns);
-        mUsedInFixMask = generateMask(mPrns);
-    }
-
-    private static GpsSatellite getSatellite(GpsStatus status, int prn) {
-        for (GpsSatellite satellite : status.getSatellites()) {
-            if (satellite.getPrn() == prn) {
-                return satellite;
-            }
-        }
-        return null;
-    }
-
-    private static String getSatelliteAssertInfo(int index, int prn, String param) {
-        return String.format("Satellite::%s [i=%d, prn=%d]", param, index, prn);
-    }
-}
diff --git a/location/tests/locationtests/src/android/location/SatelliteInfo.java b/location/tests/locationtests/src/android/location/SatelliteInfo.java
new file mode 100644
index 0000000..b6453ef
--- /dev/null
+++ b/location/tests/locationtests/src/android/location/SatelliteInfo.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import java.util.List;
+
+/*
+ * Helper class to store single Satellite info, only used it in the unit test.
+ */
+public class SatelliteInfo {
+  private static final int SVID_MAX_BIT_INDEX = 32;
+  private static final int SVID_SHIFT_WIDTH = 8;
+  private static final int CONSTELLATION_TYPE_SHIFT_WIDTH = 4;
+
+  // Index for the bits in mSvidWithFlag
+  private static final int GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA_BIT_INDEX = 0;
+  private static final int GNSS_SV_FLAGS_HAS_ALMANAC_DATA_BIT_INDEX = 1;
+  private static final int GNSS_SV_FLAGS_USED_IN_FIX_BIT_INDEX = 2;
+  private static final int GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY_BIT_INDEX = 3;
+  public int mSvid;
+  public int mSvidWithFlag;
+  public float mCn0DbHz;
+  public float mElevation;
+  public float mAzimuth;
+  public float mCarrierFrequency;
+
+  /*
+   * Flag fields, it stores the same information as svidWithFlag, but in different format, easy for
+   * the unit test.
+   */
+  public int mConstellationType;
+  public boolean mHasEphemris;
+  public boolean mHasAlmanac;
+  public boolean mUsedInFix;
+  public boolean mHasCarriesFrequency;
+
+  public SatelliteInfo(int svid, int constellationType, boolean hasEphemris, boolean hasAlmanac,
+      boolean usedInFix, boolean hasCarriesFrequency, float cn0, float elevation, float azimuth,
+      float carrierFrequency) {
+    mSvidWithFlag =
+        setRange(mSvidWithFlag, constellationType, CONSTELLATION_TYPE_SHIFT_WIDTH, SVID_SHIFT_WIDTH);
+    mSvidWithFlag = setRange(mSvidWithFlag, svid, SVID_SHIFT_WIDTH, SVID_MAX_BIT_INDEX);
+    mSvidWithFlag = setBit(mSvidWithFlag, hasEphemris, GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA_BIT_INDEX);
+    mSvidWithFlag = setBit(mSvidWithFlag, hasAlmanac, GNSS_SV_FLAGS_HAS_ALMANAC_DATA_BIT_INDEX);
+    mSvidWithFlag = setBit(mSvidWithFlag, usedInFix, GNSS_SV_FLAGS_USED_IN_FIX_BIT_INDEX);
+    mSvidWithFlag =
+        setBit(mSvidWithFlag, hasCarriesFrequency, GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY_BIT_INDEX);
+    this.mSvid = svid;
+    this.mConstellationType = constellationType;
+    this.mCn0DbHz = cn0;
+    this.mElevation = elevation;
+    this.mAzimuth = azimuth;
+    this.mCarrierFrequency = carrierFrequency;
+    this.mHasEphemris = hasEphemris;
+    this.mHasAlmanac = hasAlmanac;
+    this.mUsedInFix = usedInFix;
+    this.mHasCarriesFrequency = hasCarriesFrequency;
+  }
+
+  /*
+   * Gernerate svidWithFlags array from svInfos
+   */
+  public static int[] getSvidWithFlagsArray(List<SatelliteInfo> svInfos) {
+    int[] svidWithFlags = new int[svInfos.size()];
+    for (int i = 0; i< svInfos.size(); i++) {
+      svidWithFlags[i] = svInfos.get(i).mSvidWithFlag;
+    }
+    return svidWithFlags;
+  }
+
+  /*
+   * Gernerate cn0s array from svInfos
+   */
+  public static float[] getCn0sArray(List<SatelliteInfo> svInfos) {
+    float[] cn0s = new float[svInfos.size()];
+    for (int i = 0; i< svInfos.size(); i++) {
+      cn0s[i] = svInfos.get(i).mCn0DbHz;
+    }
+    return cn0s;
+  }
+
+  /*
+   * Gernerate elevations array from svInfos
+   */
+  public static float[] getElevationsArray(List<SatelliteInfo> svInfos) {
+    float[] elevations = new float[svInfos.size()];
+    for (int i = 0; i< svInfos.size(); i++) {
+      elevations[i] = svInfos.get(i).mElevation;
+    }
+    return elevations;
+  }
+
+  /*
+   * Gernerate azimuths array from svInfos
+   */
+  public static float[] getAzimuthsArray(List<SatelliteInfo> svInfos) {
+    float[] azimuths = new float[svInfos.size()];
+    for (int i = 0; i< svInfos.size(); i++) {
+      azimuths[i] = svInfos.get(i).mAzimuth;
+    }
+    return azimuths;
+  }
+
+  /*
+   * Gernerate carrierFrequency array from svInfos
+   */
+  public static float[] getCarrierFrequencyArray(List<SatelliteInfo> svInfos) {
+    float[] carrierFrequencies = new float[svInfos.size()];
+    for (int i = 0; i< svInfos.size(); i++) {
+      carrierFrequencies[i] = svInfos.get(i).mCarrierFrequency;
+    }
+    return carrierFrequencies;
+  }
+
+  private int setBit(int targetValue, boolean value, int index) {
+    if (value) {
+      targetValue = targetValue | (1 << index);
+    } else {
+      targetValue = targetValue & ~(1 << index);
+    }
+    return targetValue;
+  }
+
+  /*
+   * Set the bit in the range [fromIndex, toIndex), index start from the lowest bit.
+   * value -> 1 1 0 1 1 0 1 0
+   * index -> 7 6 5 4 3 2 1 0
+   * This function will set the bit in the range to the lowest X bits of the value.
+   */
+  private int setRange(int targetValue, int value, int fromIndex, int toIndex) {
+    int rangeLen = toIndex - fromIndex;
+    int valueMask = (1 << rangeLen) -1;
+    value &= valueMask;
+    value = value << fromIndex;
+    valueMask = valueMask << fromIndex;
+    targetValue &= (~valueMask);
+    targetValue |= value;
+    return targetValue;
+  }
+
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 1feea89..12e5744 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -91,10 +91,10 @@
  * are only decrypted when the samples are delivered to the decoder.
  * <p>
  * MediaDrm methods throw {@link android.media.MediaDrm.MediaDrmStateException}
- * when a method is called on a MediaDrm object that has had an unrecoverable failure 
- * in the DRM plugin or security hardware. 
- * {@link android.media.MediaDrm.MediaDrmStateException} extends 
- * {@link java.lang.IllegalStateException} with the addition of a developer-readable 
+ * when a method is called on a MediaDrm object that has had an unrecoverable failure
+ * in the DRM plugin or security hardware.
+ * {@link android.media.MediaDrm.MediaDrmStateException} extends
+ * {@link java.lang.IllegalStateException} with the addition of a developer-readable
  * diagnostic information string associated with the exception.
  * <p>
  * In the event of a mediaserver process crash or restart while a MediaDrm object
@@ -102,9 +102,9 @@
  * To recover, the app must release the MediaDrm object, then create and initialize
  * a new one.
  * <p>
- * As {@link android.media.MediaDrmResetException} and 
- * {@link android.media.MediaDrm.MediaDrmStateException} both extend 
- * {@link java.lang.IllegalStateException}, they should be in an earlier catch() 
+ * As {@link android.media.MediaDrmResetException} and
+ * {@link android.media.MediaDrm.MediaDrmStateException} both extend
+ * {@link java.lang.IllegalStateException}, they should be in an earlier catch()
  * block than {@link java.lang.IllegalStateException} if handled separately.
  * <p>
  * <a name="Callbacks"></a>
@@ -165,7 +165,7 @@
 
     /**
      * Query if the given scheme identified by its UUID is supported on
-     * this device, and whether the drm plugin is able to handle the
+     * this device, and whether the DRM plugin is able to handle the
      * media container format specified by mimeType.
      * @param uuid The UUID of the crypto scheme.
      * @param mimeType The MIME type of the media container, e.g. "video/mp4"
@@ -745,7 +745,7 @@
      * returned in KeyRequest.defaultUrl.
      * <p>
      * After the app has received the key request response from the server,
-     * it should deliver to the response to the DRM engine plugin using the method
+     * it should deliver to the response to the MediaDrm instance using the method
      * {@link #provideKeyResponse}.
      *
      * @param scope may be a sessionId or a keySetId, depending on the specified keyType.
@@ -781,7 +781,7 @@
 
     /**
      * A key response is received from the license server by the app, then it is
-     * provided to the DRM engine plugin using provideKeyResponse.  When the
+     * provided to the MediaDrm instance using provideKeyResponse.  When the
      * response is for an offline key request, a keySetId is returned that can be
      * used to later restore the keys to a new session with the method
      * {@link #restoreKeys}.
@@ -829,7 +829,7 @@
      * in the form of {name, value} pairs.  Since DRM license policies vary by vendor,
      * the specific status field names are determined by each DRM vendor.  Refer to your
      * DRM provider documentation for definitions of the field names for a particular
-     * DRM engine plugin.
+     * DRM plugin.
      *
      * @param sessionId the session ID for the DRM session
      */
@@ -897,11 +897,11 @@
            @NonNull String certAuthority);
 
     /**
-     * After a provision response is received by the app, it is provided to the DRM
-     * engine plugin using this method.
+     * After a provision response is received by the app, it is provided to the
+     * MediaDrm instance using this method.
      *
      * @param response the opaque provisioning response byte array to provide to the
-     * DRM engine plugin.
+     * MediaDrm instance.
      *
      * @throws DeniedByServerException if the response indicates that the
      * server rejected the request
@@ -912,7 +912,6 @@
     }
 
     @NonNull
-    /* could there be a valid response with 0-sized certificate or key? */
     private native Certificate provideProvisionResponseNative(@NonNull byte[] response)
             throws DeniedByServerException;
 
@@ -953,26 +952,26 @@
     /**
      * Remove all secure stops without requiring interaction with the server.
      */
-     public native void releaseAllSecureStops();
+    public native void releaseAllSecureStops();
 
     /**
-     * String property name: identifies the maker of the DRM engine plugin
+     * String property name: identifies the maker of the DRM plugin
      */
     public static final String PROPERTY_VENDOR = "vendor";
 
     /**
-     * String property name: identifies the version of the DRM engine plugin
+     * String property name: identifies the version of the DRM plugin
      */
     public static final String PROPERTY_VERSION = "version";
 
     /**
-     * String property name: describes the DRM engine plugin
+     * String property name: describes the DRM plugin
      */
     public static final String PROPERTY_DESCRIPTION = "description";
 
     /**
      * String property name: a comma-separated list of cipher and mac algorithms
-     * supported by CryptoSession.  The list may be empty if the DRM engine
+     * supported by CryptoSession.  The list may be empty if the DRM
      * plugin does not support CryptoSession operations.
      */
     public static final String PROPERTY_ALGORITHMS = "algorithms";
@@ -988,7 +987,7 @@
     public @interface StringProperty {}
 
     /**
-     * Read a DRM engine plugin String property value, given the property name string.
+     * Read a MediaDrm String property value, given the property name string.
      * <p>
      * Standard fields names are:
      * {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION},
@@ -998,6 +997,13 @@
     public native String getPropertyString(@NonNull @StringProperty String propertyName);
 
     /**
+     * Set a MediaDrm String property value, given the property name string
+     * and new value for the property.
+     */
+    public native void setPropertyString(@NonNull @StringProperty String propertyName,
+            @NonNull String value);
+
+    /**
      * Byte array property name: the device unique identifier is established during
      * device provisioning and provides a means of uniquely identifying each device.
      */
@@ -1011,7 +1017,7 @@
     public @interface ArrayProperty {}
 
     /**
-     * Read a DRM engine plugin byte array property value, given the property name string.
+     * Read a MediaDrm byte array property value, given the property name string.
      * <p>
      * Standard fields names are {@link #PROPERTY_DEVICE_UNIQUE_ID}
      */
@@ -1019,17 +1025,13 @@
     public native byte[] getPropertyByteArray(@ArrayProperty String propertyName);
 
     /**
-     * Set a DRM engine plugin String property value.
-     */
-    public native void setPropertyString(
-            String propertyName, @NonNull String value);
-
-    /**
-     * Set a DRM engine plugin byte array property value.
-     */
-    public native void setPropertyByteArray(
+    * Set a MediaDrm byte array property value, given the property name string
+    * and new value for the property.
+    */
+    public native void setPropertyByteArray(@NonNull @ArrayProperty
             String propertyName, @NonNull byte[] value);
 
+
     private static final native void setCipherAlgorithmNative(
             @NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
 
@@ -1158,7 +1160,7 @@
      * The algorithm string conforms to JCA Standard Names for Mac
      * Algorithms and is case insensitive.  For example "HmacSHA256".
      * <p>
-     * The list of supported algorithms for a DRM engine plugin can be obtained
+     * The list of supported algorithms for a DRM plugin can be obtained
      * using the method {@link #getPropertyString} with the property name
      * "algorithms".
      */
@@ -1272,7 +1274,7 @@
      * storage, and used when invoking the signRSA method.
      *
      * @param response the opaque certificate response byte array to provide to the
-     * DRM engine plugin.
+     * MediaDrm instance.
      *
      * @throws DeniedByServerException if the response indicates that the
      * server rejected the request
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index bdc0fda..31eb948 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -34,6 +34,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Set;
+import java.util.Objects;
 
 /**
  * Contains metadata about an item, such as the title, artist, etc.
@@ -616,6 +617,71 @@
             };
 
     /**
+     * Compares the contents of this object to another MediaMetadata object. It
+     * does not compare Bitmaps and Ratings as the media player can choose to
+     * forgo these fields depending on how you retrieve the MediaMetadata.
+     *
+     * @param o The Metadata object to compare this object against
+     * @return Whether or not the two objects have matching fields (excluding
+     * Bitmaps and Ratings)
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof MediaMetadata)) {
+            return false;
+        }
+
+        final MediaMetadata m = (MediaMetadata) o;
+
+        for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
+            String key = METADATA_KEYS_TYPE.keyAt(i);
+            switch (METADATA_KEYS_TYPE.valueAt(i)) {
+                case METADATA_TYPE_TEXT:
+                    if (!Objects.equals(getString(key), m.getString(key))) {
+                        return false;
+                    }
+                    break;
+                case METADATA_TYPE_LONG:
+                    if (getLong(key) != m.getLong(key)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    // Ignore ratings and bitmaps when comparing
+                    break;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hashCode = 17;
+
+        for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
+            String key = METADATA_KEYS_TYPE.keyAt(i);
+            switch (METADATA_KEYS_TYPE.valueAt(i)) {
+                case METADATA_TYPE_TEXT:
+                    hashCode = 31 * hashCode + Objects.hash(getString(key));
+                    break;
+                case METADATA_TYPE_LONG:
+                    hashCode = 31 * hashCode + Long.hashCode(getLong(key));
+                    break;
+                default:
+                    // Ignore ratings and bitmaps when comparing
+                    break;
+            }
+        }
+
+        return hashCode;
+    }
+
+    /**
      * Use to build MediaMetadata objects. The system defined metadata keys must
      * use the appropriate data type.
      */
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
index c546fcee..a52a733 100644
--- a/packages/CarrierDefaultApp/res/values-sw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -5,7 +5,7 @@
     <string name="android_system_label" msgid="2797790869522345065">"Mtoa Huduma za Simu"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Data ya mtandao wa simu imekwisha"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Data yako ya mtandao wa simu imezimwa"</string>
-    <string name="portal_notification_detail" msgid="2295729385924660881">"Gonga ili utembelee tovuti ya %s"</string>
+    <string name="portal_notification_detail" msgid="2295729385924660881">"Gusa ili utembelee tovuti ya %s"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Tafadhali wasiliana na mtoa huduma wako %s"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Hakuna muunganisho wa data kwa simu za mkononi"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"Ongeza mpango wa mitandao mingine au data kupitia %s"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index ec34253..e0a979d 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -24,7 +24,7 @@
     <string name="wifi_security_none" msgid="7985461072596594400">"لا شيء"</string>
     <string name="wifi_remembered" msgid="4955746899347821096">"تم الحفظ"</string>
     <string name="wifi_disabled_generic" msgid="4259794910584943386">"معطلة"</string>
-    <string name="wifi_disabled_network_failure" msgid="2364951338436007124">"‏أخفقت تهيئة عنوان IP"</string>
+    <string name="wifi_disabled_network_failure" msgid="2364951338436007124">"‏تعذّرت تهيئة عنوان IP"</string>
     <string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"الجهاز غير متصل بسبب انخفاض جودة الشبكة"</string>
     <string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"‏تعذّر اتصال WiFi"</string>
     <string name="wifi_disabled_password_failure" msgid="8659805351763133575">"حدثت مشكلة في المصادقة"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index bb5d2d8..1b78d30 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Impossible d\'établir l\'association avec <xliff:g id="DEVICE_NAME">%1$s</xliff:g> en raison d\'un NIP ou d\'une clé d\'accès incorrects."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Impossible d\'établir la communication avec <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Association refusée par <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"Ordinateur"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"Écouteurs"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"Téléphone"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"Imagerie"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"Écouteurs"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"Périphérique d\'entrée"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi désactivé."</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi déconnecté."</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fi : une barre."</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec audio Bluetooth LDAC : qualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Sélectionner le codec audio Bluetooth LDAC :\nQualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Diffusion : <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"DNS par TLS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"Si cette option est activée, essayez le DNS par TLS sur le port 853."</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS privé"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"Sélectionnez le mode DNS privé"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"Désactivé"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"Opportuniste"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"Nom d\'hôte du fournisseur DNS privé"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"Entrez le nom d\'hôte du fournisseur DNS"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options pour la certification d\'affichage sans fil"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler davantage les données Wi-Fi, afficher par SSID RSSI dans sélect. Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si cette option est activée, le passage du Wi-Fi aux données cellulaires est forcé lorsque le signal Wi-Fi est faible"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 308e97d8..b66f95f 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -48,7 +48,7 @@
     <string name="speed_label_okay" msgid="2331665440671174858">"OK"</string>
     <string name="speed_label_medium" msgid="3175763313268941953">"普通"</string>
     <string name="speed_label_fast" msgid="7715732164050975057">"速い"</string>
-    <string name="speed_label_very_fast" msgid="2265363430784523409">"とても速い"</string>
+    <string name="speed_label_very_fast" msgid="2265363430784523409">"非常に速い"</string>
     <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="6557104142667339895">"切断"</string>
     <string name="bluetooth_disconnecting" msgid="8913264760027764974">"切断中..."</string>
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"PINまたはパスキーが正しくないため、<xliff:g id="DEVICE_NAME">%1$s</xliff:g>をペアに設定できませんでした。"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>と通信できません。"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"ペア設定が<xliff:g id="DEVICE_NAME">%1$s</xliff:g>に拒否されました。"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"コンピュータ"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"ヘッドセット"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"スマートフォン"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"画像"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"ヘッドフォン"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"入力用周辺機器"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-FiはOFFです。"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fiが切断されました。"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fiはレベル1です。"</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth オーディオ LDAC コーデック: 再生音質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth オーディオ LDAC コーデックを選択:\n再生音質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ストリーミング: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"この設定を有効にした場合、ポート 853 で DNS over TLS を試行します。"</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"プライベート DNS"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"プライベート DNS モードを選択"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"OFF"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"日和見"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"プライベート DNS プロバイダのホスト名"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"DNS プロバイダのホスト名を入力"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ワイヤレスディスプレイ認証のオプションを表示"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fiログレベルを上げて、Wi-Fi選択ツールでSSID RSSIごとに表示します"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ON にすると、Wi-Fi の電波強度が弱い場合は強制的にモバイルデータ接続に切り替わるようになります"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 4e3bc33..5a9bbc5 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"PIN 또는 패스키가 잘못되어 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 페어링하지 못했습니다."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>와(과) 통신할 수 없습니다."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>에서 페어링을 거부했습니다."</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"컴퓨터"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"헤드셋"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"전화"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"이미징"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"헤드폰"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"입력 주변기기"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"블루투스"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi가 꺼져 있습니다."</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi 연결이 끊어졌습니다."</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fi 신호 막대가 한 개입니다."</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"블루투스 오디오 LDAC 코덱: 재생 품질"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"블루투스 오디오 LDAC 코덱 선택:\n재생 품질"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"스트리밍: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"TLS를 통한 DNS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"사용 설정한 경우, 포트 853에서 TLS를 통해 DNS를 시도합니다."</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"비공개 DNS"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"비공개 DNS 모드 선택"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"사용 안함"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"기회주의적"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"비공개 DNS 제공업체 호스트 이름"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"DNS 제공업체의 호스트 이름 입력"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"무선 디스플레이 인증서 옵션 표시"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi 로깅 수준을 높이고, Wi‑Fi 선택도구에서 SSID RSSI당 값을 표시합니다."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"사용 설정하면 Wi-Fi 신호가 약할 때 데이터 연결을 Wi-Fi에서 모바일 네트워크로 더욱 적극적으로 핸드오버합니다."</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index a1beef6..2b31bf1 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -316,7 +316,7 @@
     <string name="enable_freeform_support_summary" msgid="8247310463288834487">"Ruhusu uwezo wa kutumia madirisha ya majaribio yenye muundo huru."</string>
     <string name="local_backup_password_title" msgid="3860471654439418822">"Nenosiri la hifadhi rudufu ya eneo kazi"</string>
     <string name="local_backup_password_summary_none" msgid="6951095485537767956">"Hifadhi rudufu kamili za eneo kazi hazijalindwa kwa sasa"</string>
-    <string name="local_backup_password_summary_change" msgid="5376206246809190364">"Gonga ili ubadilishe au uondoe nenosiri la hifadhi rudufu kamili za eneo kazi"</string>
+    <string name="local_backup_password_summary_change" msgid="5376206246809190364">"Gusa ili ubadilishe au uondoe nenosiri la hifadhi rudufu kamili za eneo kazi"</string>
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"Nenosiri jipya la hifadhi rudufu limewekwa"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"Nenosiri jipya na uthibitisho havioani"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"Imeshindwa kuweka nenosiri la hifadhi rudufu"</string>
@@ -331,8 +331,8 @@
     <item msgid="5363960654009010371">"Rangi zilizoboreshwa kwa ajili ya maudhui dijitali"</item>
   </string-array>
     <string name="inactive_apps_title" msgid="1317817863508274533">"Programu zilizozimwa"</string>
-    <string name="inactive_app_inactive_summary" msgid="5091363706699855725">"Haitumika. Gonga ili ugeuze."</string>
-    <string name="inactive_app_active_summary" msgid="4174921824958516106">"Inatumika. Gonga ili ugeuze."</string>
+    <string name="inactive_app_inactive_summary" msgid="5091363706699855725">"Haitumika. Gusa ili ugeuze."</string>
+    <string name="inactive_app_active_summary" msgid="4174921824958516106">"Inatumika. Gusa ili ugeuze."</string>
     <string name="runningservices_settings_title" msgid="8097287939865165213">"Huduma zinazoendeshwa"</string>
     <string name="runningservices_settings_summary" msgid="854608995821032748">"Onyesha na dhibiti huduma zinazoendeshwa kwa sasa"</string>
     <string name="select_webview_provider_title" msgid="4628592979751918907">"Utekelezaji wa WebView"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 5ff39b6..2db6178 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"ไม่สามารถจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ได้เพราะ PIN หรือรหัสผ่านไม่ถูกต้อง"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"ไม่สามารถเชื่อมต่อกับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ปฏิเสธการจับคู่อุปกรณ์"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"คอมพิวเตอร์"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"ชุดหูฟัง"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"โทรศัพท์"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"การถ่ายภาพ"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"หูฟัง"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"อุปกรณ์อินพุต"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"บลูทูธ"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi ปิดอยู่"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"สัญญาณ Wi-Fi 1 ขีด"</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"เลือกตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC:\nคุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"DNS ผ่าน TLS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"หากเปิดใช้แล้ว ให้ลองใช้ DNS ผ่าน TLS บนพอร์ต 853"</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS ส่วนตัว"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"เลือกโหมด DNS ส่วนตัว"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"ปิด"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"ที่ใช้โอกาส"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"ชื่อโฮสต์ของผู้ให้บริการ DNS ส่วนตัว"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"ป้อนชื่อโฮสต์ของผู้ให้บริการ DNS"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"แสดงตัวเลือกสำหรับการรับรองการแสดงผล แบบไร้สาย"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"เมื่อเปิดใช้แล้ว Wi-Fi จะส่งผ่านการเชื่อมต่อข้อมูลไปยังเครือข่ายมือถือเมื่อสัญญาณ Wi-Fi อ่อน"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 2554388..2215fe5 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -99,6 +99,13 @@
     <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 碼或密碼金鑰不正確。"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 通訊。"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」拒絕配對要求。"</string>
+    <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"電腦"</string>
+    <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"免持耳機"</string>
+    <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"電話"</string>
+    <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"顯像裝置"</string>
+    <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"頭戴式耳機"</string>
+    <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"周邊輸入裝置"</string>
+    <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"藍牙"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"已關閉 Wi-Fi。"</string>
     <string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi 連線已中斷。"</string>
     <string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wi-Fi 訊號強度一格。"</string>
@@ -209,8 +216,12 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"藍牙音訊 LDAC 轉碼器:播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"選取藍牙音訊 LDAC 轉碼器:\n播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"串流中:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
-    <string name="dns_tls" msgid="6773814174391131955">"透過傳輸層安全標準 (TLS) 執行 DNS"</string>
-    <string name="dns_tls_summary" msgid="3692494150251071380">"如果啟用這個選項,即可在通訊埠 853 上嘗試透過傳輸層安全標準 (TLS) 執行 DNS。"</string>
+    <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"私人 DNS"</string>
+    <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"選取私人 DNS 模式"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"停用"</string>
+    <string name="private_dns_mode_opportunistic" msgid="7608409735589131766">"隨機"</string>
+    <string name="private_dns_mode_provider" msgid="8354935160639360804">"私人 DNS 供應商主機名稱"</string>
+    <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"輸入 DNS 供應商的主機名稱"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"啟用時,Wi-Fi 連線在訊號不穩的情況下會更積極轉換成行動數據連線"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4bb16ff..d82db41 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -77,7 +77,7 @@
     <!-- Summary for the network but no internet connection was detected. -->
     <string name="wifi_no_internet_no_reconnect">Won\'t automatically connect</string>
     <!-- Summary for the remembered network but no internet connection was detected. -->
-    <string name="wifi_no_internet">No Internet access</string>
+    <string name="wifi_no_internet">No internet access</string>
     <!-- Summary for saved networks -->
     <string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
 
@@ -95,7 +95,7 @@
     <string name="certinstaller_package" translatable="false">com.android.certinstaller</string>
 
     <!-- Summary for Connected wifi network without internet -->
-    <string name="wifi_connected_no_internet">Connected, no Internet</string>
+    <string name="wifi_connected_no_internet">Connected, no internet</string>
 
     <!-- Summary for networks failing to connect due to association rejection status 17, AP full -->
     <string name="wifi_ap_unable_to_handle_new_sta">Access point temporarily full</string>
@@ -192,14 +192,14 @@
     <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
     <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string>
     <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
-    <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string>
+    <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for internet access</string>
     <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
-    <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string>
+    <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local internet connection with device</string>
 
     <!-- Bluetooth settings. Connection options screen. The summary
          for the PAN checkbox preference that describes how checking it
          will set the PAN profile as preferred. -->
-    <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
+    <string name="bluetooth_pan_profile_summary_use_for">Use for internet access</string>
     <!-- Bluetooth settings. Connection options screen.  The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
     <string name="bluetooth_map_profile_summary_use_for">Use for map</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the sap checkbox preference that describes how checking it will set the sap profile as preferred. -->
@@ -732,6 +732,11 @@
     <!-- UI debug setting: profile time taken by hardware acceleration to render apps [CHAR LIMIT=25] -->
     <string name="track_frame_time">Profile GPU rendering</string>
 
+    <!-- UI debug setting: enable gpu debug layers [CHAR LIMIT=25] -->
+    <string name="enable_gpu_debug_layers">Enable GPU debug layers</string>
+    <!-- UI debug setting: enable gpu debug layers summary [CHAR LIMIT=50] -->
+    <string name="enable_gpu_debug_layers_summary">Allow loading GPU debug layers for debug apps</string>
+
     <!-- UI debug setting: scaling factor for window animations [CHAR LIMIT=25] -->
     <string name="window_animation_scale_title">Window animation scale</string>
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 41b205b..ba1c9e3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -869,6 +869,15 @@
         dumpSetting(s, p,
                 Settings.Global.WAIT_FOR_DEBUGGER,
                 GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+        dumpSetting(s, p,
+                Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
+                GlobalSettingsProto.ENABLE_GPU_DEBUG_LAYERS);
+        dumpSetting(s, p,
+                Settings.Global.GPU_DEBUG_APP,
+                GlobalSettingsProto.GPU_DEBUG_APP);
+        dumpSetting(s, p,
+                Settings.Global.GPU_DEBUG_LAYERS,
+                GlobalSettingsProto.GPU_DEBUG_LAYERS);
         // Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Global.LOW_POWER_MODE,
diff --git a/packages/Shell/res/values-sw/strings.xml b/packages/Shell/res/values-sw/strings.xml
index 3f3dd2a..e4dcbee 100644
--- a/packages/Shell/res/values-sw/strings.xml
+++ b/packages/Shell/res/values-sw/strings.xml
@@ -24,10 +24,10 @@
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"Tafadhali subiri…"</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Tutatuma ripoti ya hitilafu kwenye simu baada ya muda mfupi"</string>
     <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Chagua kushiriki ripoti ya hitilafu"</string>
-    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Gonga ili ushiriki ripoti yako ya hitilafu"</string>
+    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Gusa ili ushiriki ripoti yako ya hitilafu"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Chagua kushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Gonga ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Gonga ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Gusa ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Gusa ili ushiriki ripoti yako ya hitilafu bila picha ya skrini au usubiri picha ya skrini itayarishwe"</string>
     <string name="bugreport_confirm" msgid="5917407234515812495">"Ripoti za hitilafu zinajumuisha data kutoka faili za kumbukumbu mbalimbali zilizo kwenye mfumo, ambazo huenda zinajumuisha data ambayo unachukulia kuwa nyeti (kama vile matumizi ya programu na maelezo kuhusu data ilipo). Shiriki ripoti za hitilafu na watu na programu unazoamini pekee."</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Usionyeshe tena"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"Ripoti za hitilafu"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 04ce752..839fb87 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -117,8 +117,8 @@
       <item quantity="other">‏رمز PUK لشريحة SIM غير صحيح، ويتبقى لديك <xliff:g id="NUMBER_1">%d</xliff:g> من المحاولات تصبح بعدها شريحة SIM غير صالحة للاستخدام بشكل دائم.</item>
       <item quantity="one">‏رمز PUK لشريحة SIM غير صالح، ويتبقى لديك محاولة واحدة (<xliff:g id="NUMBER_0">%d</xliff:g>)، تصبح بعدها شريحة SIM غير صالحة للاستخدام بشكل دائم.</item>
     </plurals>
-    <string name="kg_password_pin_failed" msgid="8769990811451236223">"‏أخفقت عملية \"رقم التعريف الشخصي\" لشريحة SIM"</string>
-    <string name="kg_password_puk_failed" msgid="1331621440873439974">"‏أخفقت عملية PUK لشريحة SIM"</string>
+    <string name="kg_password_pin_failed" msgid="8769990811451236223">"‏تعذّر إتمام عملية \"رقم التعريف الشخصي\" لشريحة SIM"</string>
+    <string name="kg_password_puk_failed" msgid="1331621440873439974">"‏تعذّر إتمام عملية PUK لشريحة SIM"</string>
     <string name="kg_pin_accepted" msgid="7637293533973802143">"تم قبول الرمز"</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"لا تتوفر خدمة."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"تبديل أسلوب الإدخال"</string>
diff --git a/packages/SystemUI/res/values-ar/config.xml b/packages/SystemUI/res/values-ar/config.xml
index 5309563..477f219 100644
--- a/packages/SystemUI/res/values-ar/config.xml
+++ b/packages/SystemUI/res/values-ar/config.xml
@@ -22,5 +22,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="config_overviewServiceComponent" msgid="2288311504315574053">"com.android.launcher3/com.android.quickstep.TouchInteractionService"</string>
     <string name="doze_pickup_subtype_performs_proximity_check" msgid="533127617385956583"></string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index b5ac31a..d642118 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -55,15 +55,15 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"تم إنشاء الاتصال بالإنترنت عن طريق البلوتوث."</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"إعداد أسلوب الإدخال"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"لوحة مفاتيح فعلية"</string>
-    <string name="usb_device_permission_prompt" msgid="834698001271562057">"‏هل تريد السماح للتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى جهاز USB؟"</string>
-    <string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"‏هل تريد السماح للتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى ملحق USB؟"</string>
-    <string name="usb_device_confirm_prompt" msgid="5161205258635253206">"‏هل تريد فتح <xliff:g id="ACTIVITY">%1$s</xliff:g> عند توصيل جهاز USB هذا؟"</string>
-    <string name="usb_accessory_confirm_prompt" msgid="3808984931830229888">"‏هل تريد فتح <xliff:g id="ACTIVITY">%1$s</xliff:g> عند توصيل ملحق USB هذا؟"</string>
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>؟"</string>
+    <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"هل تريد فتح <xliff:g id="APPLICATION">%1$s</xliff:g> للتعامل مع <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟"</string>
+    <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"هل تريد فتح تطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> للتعامل مع <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>؟"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"‏لا يعمل أي تطبيق مثبت مع ملحق UEB هذا. مزيد من المعلومات عن هذا الملحق على <xliff:g id="URL">%1$s</xliff:g>."</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"‏ملحق USB"</string>
     <string name="label_view" msgid="6304565553218192990">"عرض"</string>
-    <string name="always_use_device" msgid="1450287437017315906">"‏الاستخدام بشكل افتراضي لجهاز USB هذا"</string>
-    <string name="always_use_accessory" msgid="1210954576979621596">"‏الاستخدام بشكل افتراضي لملحق USB هذا"</string>
+    <string name="always_use_device" msgid="4015357883336738417">"فتح تطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> دائمًا عند توصيل <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string>
+    <string name="always_use_accessory" msgid="3257892669444535154">"فتح تطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> دائمًا عند توصيل <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string>
     <string name="usb_debugging_title" msgid="4513918393387141949">"‏هل تريد السماح بتصحيح أخطاء USB؟"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"‏الملف المرجعي الرئيسي لـ RSA في هذا الكمبيوتر هو:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"السماح دائمًا من هذا الكمبيوتر"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 989f6d1..6e1feea 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -51,21 +51,15 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"Connexion Bluetooth partagée"</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurer les modes de saisie"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Clavier physique"</string>
-    <!-- no translation found for usb_device_permission_prompt (1825685909587559679) -->
-    <skip />
-    <!-- no translation found for usb_accessory_permission_prompt (2465531696941369047) -->
-    <skip />
-    <!-- no translation found for usb_device_confirm_prompt (7440562274256843905) -->
-    <skip />
-    <!-- no translation found for usb_accessory_confirm_prompt (4333670517539993561) -->
-    <skip />
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
+    <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"Ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> pour utiliser <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"Ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> pour utiliser <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"Aucune application installée compatible avec accessoire USB. En savoir plus sur <xliff:g id="URL">%1$s</xliff:g>"</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"Accessoire USB"</string>
     <string name="label_view" msgid="6304565553218192990">"Afficher"</string>
-    <!-- no translation found for always_use_device (4015357883336738417) -->
-    <skip />
-    <!-- no translation found for always_use_accessory (3257892669444535154) -->
-    <skip />
+    <string name="always_use_device" msgid="4015357883336738417">"Toujours ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> lorsque <xliff:g id="USB_DEVICE">%2$s</xliff:g> est connecté"</string>
+    <string name="always_use_accessory" msgid="3257892669444535154">"Toujours ouvrir <xliff:g id="APPLICATION">%1$s</xliff:g> lorsque <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> est connecté"</string>
     <string name="usb_debugging_title" msgid="4513918393387141949">"Autoriser le débogage USB?"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"Empreinte numérique de la clé RSA de l\'ordinateur : \n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"Toujours autoriser sur cet ordinateur"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index d1bbe50..5853d93 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -51,21 +51,15 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth imefungwa"</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Weka mbinu za ingizo"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Kibodi halisi"</string>
-    <!-- no translation found for usb_device_permission_prompt (1825685909587559679) -->
-    <skip />
-    <!-- no translation found for usb_accessory_permission_prompt (2465531696941369047) -->
-    <skip />
-    <!-- no translation found for usb_device_confirm_prompt (7440562274256843905) -->
-    <skip />
-    <!-- no translation found for usb_accessory_confirm_prompt (4333670517539993561) -->
-    <skip />
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
+    <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"Ungependa kufungua <xliff:g id="APPLICATION">%1$s</xliff:g> ili itumie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
+    <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"Ungependa kufungua <xliff:g id="APPLICATION">%1$s</xliff:g> ili itumie <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"Hakuna programu zilizosakinishwa zinazofanya kazi na kifaa hiki cha USB. Pata maelezo zaidi kuhusu kifaa hiki kwenye <xliff:g id="URL">%1$s</xliff:g>"</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"Kifaa cha Usb"</string>
     <string name="label_view" msgid="6304565553218192990">"Ona"</string>
-    <!-- no translation found for always_use_device (4015357883336738417) -->
-    <skip />
-    <!-- no translation found for always_use_accessory (3257892669444535154) -->
-    <skip />
+    <string name="always_use_device" msgid="4015357883336738417">"Fungua <xliff:g id="APPLICATION">%1$s</xliff:g> kila wakati <xliff:g id="USB_DEVICE">%2$s</xliff:g> inaunganishwa"</string>
+    <string name="always_use_accessory" msgid="3257892669444535154">"Fungua <xliff:g id="APPLICATION">%1$s</xliff:g> kila wakati <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> inaunganishwa"</string>
     <string name="usb_debugging_title" msgid="4513918393387141949">"Ruhusu utatuaji wa USB?"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"Alama ya kidole ya kitufe cha RSA ya kompyuta ni:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"Ruhusu kutoka kwenye kompyuta hii kila wakati"</string>
@@ -77,7 +71,7 @@
     <string name="screenshot_saving_title" msgid="8242282144535555697">"Inahifadhi picha ya skrini..."</string>
     <string name="screenshot_saving_text" msgid="2419718443411738818">"Picha ya skrini inahifadhiwa"</string>
     <string name="screenshot_saved_title" msgid="6461865960961414961">"Picha ya skrini imenaswa."</string>
-    <string name="screenshot_saved_text" msgid="2685605830386712477">"Gonga ili utazame picha ya skrini uliyohifadhi."</string>
+    <string name="screenshot_saved_text" msgid="2685605830386712477">"Gusa ili utazame picha ya skrini uliyohifadhi."</string>
     <string name="screenshot_failed_title" msgid="705781116746922771">"Haikuweza kunasa picha ya skrini"</string>
     <string name="screenshot_failed_to_save_unknown_text" msgid="7887826345701753830">"Hitilafu imetokea wakati wa kuhifadhi picha ya skrini."</string>
     <string name="screenshot_failed_to_save_text" msgid="2592658083866306296">"Haina nafasi ya kutosha kuhifadhi picha ya skrini."</string>
@@ -363,7 +357,7 @@
     <string name="zen_silence_introduction" msgid="3137882381093271568">"Hatua hii huzuia sauti na mitetemo YOTE, ikiwa ni pamoja na ile inayotokana na kengele, muziki, video na michezo."</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>+"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Arifa zisizo za dharura sana ziko hapo chini"</string>
-    <string name="notification_tap_again" msgid="7590196980943943842">"Gonga tena ili ufungue"</string>
+    <string name="notification_tap_again" msgid="7590196980943943842">"Gusa tena ili ufungue"</string>
     <string name="keyguard_unlock" msgid="8043466894212841998">"Telezesha kidole ili ufungue"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Kifaa hiki kinasimamiwa na shirika lako"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Kifaa hiki kinadhibitiwa na <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
@@ -500,11 +494,11 @@
     <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="2447177903892477915">"Masafa ya ishara ya kampuni ya simu"</string>
     <string name="stream_accessibility" msgid="301136219144385106">"Zana za walio na matatizo ya kuona au kusikia"</string>
-    <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. Gonga ili urejeshe."</string>
-    <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. Gonga ili uweke mtetemo. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
-    <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. Gonga ili ukomeshe. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
-    <string name="volume_stream_content_description_vibrate_a11y" msgid="6427727603978431301">"%1$s. Gonga ili uweke mtetemo."</string>
-    <string name="volume_stream_content_description_mute_a11y" msgid="8995013018414535494">"%1$s. Gonga ili usitishe."</string>
+    <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. Gusa ili urejeshe."</string>
+    <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. Gusa ili uweke mtetemo. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
+    <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. Gusa ili ukomeshe. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string>
+    <string name="volume_stream_content_description_vibrate_a11y" msgid="6427727603978431301">"%1$s. Gusa ili uweke mtetemo."</string>
+    <string name="volume_stream_content_description_mute_a11y" msgid="8995013018414535494">"%1$s. Gusa ili usitishe."</string>
     <string name="volume_dialog_accessibility_shown_message" msgid="1834631467074259998">"Inaonyesha %s ya vidhibiti vya sauti. Telezesha kidole juu ili uondoe."</string>
     <string name="volume_dialog_accessibility_dismissed_message" msgid="51543526013711399">"Imeficha vidhibiti vya sauti"</string>
     <string name="system_ui_tuner" msgid="708224127392452018">"Kirekebishi cha kiolesura cha mfumo"</string>
@@ -700,9 +694,9 @@
     <string name="accessibility_action_divider_top_50" msgid="6385859741925078668">"Juu 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="6201455163864841205">"Juu 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="301433196679548001">"Skrini nzima ya chini"</string>
-    <string name="accessibility_qs_edit_tile_label" msgid="8374924053307764245">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Gonga mara mbili ili ubadilishe."</string>
-    <string name="accessibility_qs_edit_add_tile_label" msgid="8133209638023882667">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Gonga mara mbili ili uongeze."</string>
-    <string name="accessibility_qs_edit_position_label" msgid="5055306305919289819">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>. Gonga mara mbili ili uchague."</string>
+    <string name="accessibility_qs_edit_tile_label" msgid="8374924053307764245">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Gusa mara mbili ili ubadilishe."</string>
+    <string name="accessibility_qs_edit_add_tile_label" msgid="8133209638023882667">"<xliff:g id="TILE_NAME">%1$s</xliff:g>. Gusa mara mbili ili uongeze."</string>
+    <string name="accessibility_qs_edit_position_label" msgid="5055306305919289819">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>. Gusa mara mbili ili uchague."</string>
     <string name="accessibility_qs_edit_move_tile" msgid="2461819993780159542">"Hamisha <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_qs_edit_remove_tile" msgid="7484493384665907197">"Ondoa <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="8050200862063548309">"<xliff:g id="TILE_NAME">%1$s</xliff:g> imeongezwa kwenye nafasi ya <xliff:g id="POSITION">%2$d</xliff:g>"</string>
@@ -776,7 +770,7 @@
     <string name="qs_dnd_keep" msgid="1825009164681928736">"Usibadilishe"</string>
     <string name="qs_dnd_replace" msgid="8019520786644276623">"Badilisha"</string>
     <string name="running_foreground_services_title" msgid="381024150898615683">"Programu zinatumika chinichini"</string>
-    <string name="running_foreground_services_msg" msgid="6326247670075574355">"Gonga ili upate maelezo kuhusu betri na matumizi ya data"</string>
+    <string name="running_foreground_services_msg" msgid="6326247670075574355">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
     <string name="data_usage_disable_mobile" msgid="5116269981510015864">"Ungependa kuzima data ya mtandao wa simu?"</string>
     <string name="touch_filtered_warning" msgid="8671693809204767551">"Kwa sababu programu nyingine inazuia ombi la ruhusa, hatuwezi kuthibitisha jibu lako katika Mipangilio."</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java
index 5ebb22d..4d422bb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java
@@ -17,10 +17,6 @@
 
 import static android.view.Choreographer.CALLBACK_INPUT;
 
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.Choreographer;
 
 /**
@@ -31,7 +27,7 @@
     /**
      * Posts an input callback to the choreographer.
      */
-    public static void postInputFrame(Runnable runnable) {
-        Choreographer.getInstance().postCallback(CALLBACK_INPUT, runnable, null);
+    public static void postInputFrame(Choreographer choreographer, Runnable runnable) {
+        choreographer.postCallback(CALLBACK_INPUT, runnable, null);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index c18f9b6..cb3d59c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -33,6 +33,8 @@
 import com.android.systemui.R;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 
+import java.util.Collections;
+
 /**
  * View visible under the clock on the lock screen and AoD.
  */
@@ -75,7 +77,8 @@
         super.onAttachedToWindow();
 
         // Set initial content
-        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+                Collections.emptyList()));
 
         // Make sure we always have the most current slice
         getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
@@ -91,7 +94,7 @@
 
     private void showSlice(Slice slice) {
         // Items will be wrapped into an action when they have tap targets.
-        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
         if (actionSlice != null) {
             mSlice = actionSlice.getSlice();
             mSliceAction = actionSlice.getAction();
@@ -105,7 +108,7 @@
             return;
         }
 
-        SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+        SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
         if (title == null) {
             mTitle.setVisibility(GONE);
         } else {
@@ -113,7 +116,7 @@
             mTitle.setText(title.getText());
         }
 
-        SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+        SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
         if (text == null) {
             mText.setVisibility(GONE);
         } else {
@@ -154,7 +157,8 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+                    Collections.emptyList()));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 03018f7..6ddc76b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -84,7 +84,7 @@
 
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+        return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index b585bdf..f451fda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -37,6 +37,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
@@ -75,17 +76,20 @@
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
     private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private final OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
 
     public NotificationGutsManager(
             NotificationPresenter presenter,
             NotificationStackScrollLayout stackScroller,
             NotificationInfo.CheckSaveListener checkSaveListener,
-            Context context) {
+            Context context,
+            OnSettingsClickListener onSettingsClickListener) {
         mPresenter = presenter;
         mStackScroller = stackScroller;
         mCheckSaveListener = checkSaveListener;
         mContext = context;
+        mOnSettingsClickListener = onSettingsClickListener;
         Resources res = context.getResources();
 
         mNonBlockablePkgs = new HashSet<>();
@@ -189,6 +193,7 @@
                 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
                     guts.resetFalsingCheck();
+                    mOnSettingsClickListener.onClick(sbn.getKey());
                     startAppNotificationSettingsActivity(pkg, appUid, channel);
                 };
             }
@@ -352,4 +357,8 @@
         pw.print("mKeyToRemoveOnGutsClosed: ");
         pw.println(mKeyToRemoveOnGutsClosed);
     }
+
+    public interface OnSettingsClickListener {
+        void onClick(String key);
+    }
 }
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 6775615..1bbb94a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -43,6 +43,7 @@
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
@@ -919,7 +920,14 @@
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mGutsManager = new NotificationGutsManager(this, mStackScroller,
-                mCheckSaveListener, mContext);
+                mCheckSaveListener, mContext,
+                key -> {
+                    try {
+                        mBarService.onNotificationSettingsViewed(key);
+                    } catch (RemoteException e) {
+                        // if we're here we're dead
+                    }
+                });
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1470,6 +1478,11 @@
                         }
                     }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
                 }
+                try {
+                    mBarService.onNotificationDirectReplied(entry.key);
+                } catch (RemoteException e) {
+                    // system process is dead if we're here.
+                }
             }
         });
 
@@ -1785,9 +1798,14 @@
         final int id = n.getId();
         final int userId = n.getUserId();
         try {
-            // TODO: record actual dismissal surface
+            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+            if (isHeadsUp(n.getKey())) {
+                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+            } else if (mStackScroller.hasPulsingNotifications()) {
+                dismissalSurface = NotificationStats.DISMISSAL_AOD;
+            }
             mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
-                    NotificationStats.DISMISSAL_OTHER);
+                    dismissalSurface);
             if (FORCE_REMOTE_INPUT_HISTORY
                     && mKeysKeptForRemoteInput.contains(n.getKey())) {
                 mKeysKeptForRemoteInput.remove(n.getKey());
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4b8f581..0d41e20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -207,6 +207,7 @@
         } else {
             addExistingRows();
         }
+        updateRowsH(getActiveRow());
     }
 
     private ColorStateList loadColorStateList(int colorResId) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 4437aac..4eae342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -63,7 +63,7 @@
     @Test
     public void returnsValidSlice() {
         Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
-        SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+        SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE,
                 null /* nonHints */);
         Assert.assertNotNull("Slice must provide a title.", text);
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7d..5867006d 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,7 +21,11 @@
 import android.content.Context;
 import android.hardware.input.InputManager;
 import android.os.Binder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -72,6 +76,9 @@
                 case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
                     return toggleSplitScreen();
                 }
+                case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: {
+                    return lockScreen();
+                }
             }
             return false;
         } finally {
@@ -153,4 +160,11 @@
         }
         return true;
     }
+
+    private boolean lockScreen() {
+        mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
+                PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
+        mWindowManagerService.lockNow();
+        return true;
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index af4668a..9741486 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1167,7 +1167,16 @@
                         break;
                     }
                 }
+
                 value = getSanitizedValue(sanitizers, id, value);
+                if (value == null) {
+                    if (sDebug) {
+                        Slog.d(TAG, "value of required field " + id + " failed sanitization");
+                    }
+                    allRequiredAreNotEmpty = false;
+                    break;
+                }
+                viewState.setSanitizedValue(value);
                 currentValues.put(id, value);
                 final AutofillValue filledValue = viewState.getAutofilledValue();
 
@@ -1337,7 +1346,7 @@
         return sanitizers;
     }
 
-    @NonNull
+    @Nullable
     private AutofillValue getSanitizedValue(
             @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
             @NonNull AutofillId id,
@@ -1431,10 +1440,10 @@
             if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
 
             for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
-                final ViewState state = mViewStates.valueAt(viewStateNum);
+                final ViewState viewState = mViewStates.valueAt(viewStateNum);
 
-                final AutofillId id = state.id;
-                final AutofillValue value = state.getCurrentValue();
+                final AutofillId id = viewState.id;
+                final AutofillValue value = viewState.getCurrentValue();
                 if (value == null) {
                     if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
                     continue;
@@ -1446,9 +1455,17 @@
                 }
                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
 
-                final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+                AutofillValue sanitizedValue = viewState.getSanitizedValue();
 
-                node.updateAutofillValue(sanitizedValue);
+                if (sanitizedValue == null) {
+                    // Field is optional and haven't been sanitized yet.
+                    sanitizedValue = getSanitizedValue(sanitizers, id, value);
+                }
+                if (sanitizedValue != null) {
+                    node.updateAutofillValue(sanitizedValue);
+                } else if (sDebug) {
+                    Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
+                }
             }
 
             // Sanitize structure before it's sent to service.
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 832a66b..0dbdc13 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -76,6 +76,7 @@
     private FillResponse mResponse;
     private AutofillValue mCurrentValue;
     private AutofillValue mAutofilledValue;
+    private AutofillValue mSanitizedValue;
     private Rect mVirtualBounds;
     private int mState;
     private String mDatasetId;
@@ -117,6 +118,15 @@
     }
 
     @Nullable
+    AutofillValue getSanitizedValue() {
+        return mSanitizedValue;
+    }
+
+    void setSanitizedValue(@Nullable AutofillValue value) {
+        mSanitizedValue = value;
+    }
+
+    @Nullable
     FillResponse getResponse() {
         return mResponse;
     }
@@ -218,6 +228,7 @@
         }
         pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
         pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
+        pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
         pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
     }
 }
\ No newline at end of file
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 633bb3e..3d81baf 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -43,7 +43,8 @@
     android.hardware.oemlock-V1.0-java \
     android.hardware.tetheroffload.control-V1.0-java \
     android.hardware.vibrator-V1.0-java \
-    android.hardware.configstore-V1.0-java
+    android.hardware.configstore-V1.0-java \
+    android.hardware.contexthub-V1.0-java
 
 ifneq ($(INCREMENTAL_BUILDS),)
     LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 701c574..3cd2f6a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -58,6 +58,7 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
 import com.android.server.am.ActivityManagerService.NeededUriGrants;
+import com.android.server.am.proto.ActiveServicesProto;
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -85,6 +86,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.webkit.WebViewZygote;
 
 public final class ActiveServices {
@@ -633,7 +635,7 @@
                         sb.append("Stopping service due to app idle: ");
                         UserHandle.formatUid(sb, service.appInfo.uid);
                         sb.append(" ");
-                        TimeUtils.formatDuration(service.createTime
+                        TimeUtils.formatDuration(service.createRealTime
                                 - SystemClock.elapsedRealtime(), sb);
                         sb.append(" ");
                         sb.append(compName);
@@ -3220,7 +3222,7 @@
         info.uid = r.appInfo.uid;
         info.process = r.processName;
         info.foreground = r.isForeground;
-        info.activeSince = r.createTime;
+        info.activeSince = r.createRealTime;
         info.started = r.startRequested;
         info.clientCount = r.connections.size();
         info.crashCount = r.crashCount;
@@ -3574,7 +3576,7 @@
                 pw.print("    app=");
                 pw.println(r.app);
                 pw.print("    created=");
-                TimeUtils.formatDuration(r.createTime, nowReal, pw);
+                TimeUtils.formatDuration(r.createRealTime, nowReal, pw);
                 pw.print(" started=");
                 pw.print(r.startRequested);
                 pw.print(" connections=");
@@ -3840,6 +3842,26 @@
         return new ServiceDumper(fd, pw, args, opti, dumpAll, dumpPackage);
     }
 
+    protected void writeToProto(ProtoOutputStream proto) {
+        synchronized (mAm) {
+            int[] users = mAm.mUserController.getUsers();
+            for (int user : users) {
+                ServiceMap smap = mServiceMap.get(user);
+                if (smap == null) {
+                    continue;
+                }
+                long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS);
+                proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user);
+                ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+                for (int i=0; i<alls.size(); i++) {
+                    alls.valueAt(i).writeToProto(proto,
+                            ActiveServicesProto.ServicesByUser.SERVICE_RECORDS);
+                }
+                proto.end(token);
+            }
+        }
+    }
+
     /**
      * There are three ways to call this:
      *  - no service specified: dump all the services
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c1d3a46..0a6f976 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -415,6 +415,8 @@
 import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.am.proto.ActivityManagerServiceProto;
 import com.android.server.am.proto.BroadcastProto;
+import com.android.server.am.proto.GrantUriProto;
+import com.android.server.am.proto.NeededUriGrantsProto;
 import com.android.server.am.proto.StickyBroadcastProto;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
@@ -1191,6 +1193,13 @@
             return result;
         }
 
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            proto.write(GrantUriProto.URI, uri.toString());
+            proto.write(GrantUriProto.SOURCE_USER_ID, sourceUserId);
+            proto.end(token);
+        }
+
         public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
             return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
                     ContentProvider.getUriWithoutUserId(uri), false);
@@ -2784,7 +2793,7 @@
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
-        mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
+        mActivityStarter = new ActivityStarter(this);
         mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -9047,6 +9056,19 @@
             this.targetUid = targetUid;
             this.flags = flags;
         }
+
+        void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg);
+            proto.write(NeededUriGrantsProto.TARGET_UID, targetUid);
+            proto.write(NeededUriGrantsProto.FLAGS, flags);
+
+            final int N = this.size();
+            for (int i=0; i<N; i++) {
+                this.get(i).writeToProto(proto, NeededUriGrantsProto.GRANTS);
+            }
+            proto.end(token);
+        }
     }
 
     /**
@@ -11696,6 +11718,15 @@
         return true;
     }
 
+    /**
+     * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The
+     * PackageManager could be unavailable at construction time and therefore needs to be accessed
+     * on demand.
+     */
+    IPackageManager getPackageManager() {
+        return AppGlobals.getPackageManager();
+    }
+
     PackageManagerInternal getPackageManagerInternalLocked() {
         if (mPackageManagerInt == null) {
             mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -14925,6 +14956,25 @@
                 synchronized (this) {
                     writeBroadcastsToProtoLocked(proto);
                 }
+            } else if ("provider".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                }
+                if (!dumpProviderProto(fd, pw, name, newArgs)) {
+                    pw.println("No providers match: " + name);
+                    pw.println("Use -h for help.");
+                }
+            } else if ("service".equals(cmd)) {
+                mServices.writeToProto(proto);
             } else {
                 // default option, dump everything, output is ActivityManagerServiceProto
                 synchronized (this) {
@@ -14935,6 +14985,10 @@
                     long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
                     writeBroadcastsToProtoLocked(proto);
                     proto.end(broadcastToken);
+
+                    long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
+                    mServices.writeToProto(proto);
+                    proto.end(serviceToken);
                 }
             }
             proto.flush();
@@ -16066,6 +16120,15 @@
         return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll);
     }
 
+    /**
+     * Similar to the dumpProvider, but only dumps the first matching provider.
+     * The provider is responsible for dumping as proto.
+     */
+    protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+            String[] args) {
+        return mProviderMap.dumpProviderProto(fd, pw, name, args);
+    }
+
     static class ItemMatcher {
         ArrayList<ComponentName> components;
         ArrayList<String> strings;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 9b8cbc1..03162bb 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -134,7 +134,6 @@
     private static final int INVALID_LAUNCH_MODE = -1;
 
     private final ActivityManagerService mService;
-    private final IPackageManager mPackageManager;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
 
@@ -234,9 +233,8 @@
         mIntentDelivered = false;
     }
 
-    ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
+    ActivityStarter(ActivityManagerService service) {
         mService = service;
-        mPackageManager = packageManager;
         mSupervisor = mService.mStackSupervisor;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
     }
@@ -379,7 +377,7 @@
                     && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                 try {
                     intent.addCategory(Intent.CATEGORY_VOICE);
-                    if (!mPackageManager.activitySupportsIntent(
+                    if (!mService.getPackageManager().activitySupportsIntent(
                             intent.getComponent(), intent, resolvedType)) {
                         Slog.w(TAG,
                                 "Activity being started in current voice task does not support voice: "
@@ -397,7 +395,7 @@
             // If the caller is starting a new voice session, just make sure the target
             // is actually allowing it to run this way.
             try {
-                if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
+                if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
                         intent, resolvedType)) {
                     Slog.w(TAG,
                             "Activity being started in new voice task does not support: "
diff --git a/services/core/java/com/android/server/am/AppBindRecord.java b/services/core/java/com/android/server/am/AppBindRecord.java
index df833ad..7b38597 100644
--- a/services/core/java/com/android/server/am/AppBindRecord.java
+++ b/services/core/java/com/android/server/am/AppBindRecord.java
@@ -17,6 +17,9 @@
 package com.android.server.am;
 
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
 
 import java.io.PrintWriter;
 
@@ -60,4 +63,18 @@
             + Integer.toHexString(System.identityHashCode(this))
             + " " + service.shortName + ":" + client.processName + "}";
     }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(AppBindRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        if (client != null) {
+            client.writeToProto(proto, AppBindRecordProto.CLIENT);
+        }
+        final int N = connections.size();
+        for (int i=0; i<N; i++) {
+            connections.valueAt(i).writeToProto(proto, AppBindRecordProto.CONNECTIONS);
+        }
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 9b7a0c4..6df283c 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -19,6 +19,9 @@
 import android.app.IServiceConnection;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.ConnectionRecordProto;
 
 import java.io.PrintWriter;
 
@@ -119,4 +122,70 @@
         sb.append('}');
         return stringName = sb.toString();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        if (binding == null) return; // if binding is null, don't write data, something is wrong.
+        long token = proto.start(fieldId);
+        proto.write(ConnectionRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        if (binding.client != null) {
+            proto.write(ConnectionRecordProto.USER_ID, binding.client.userId);
+        }
+        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.AUTO_CREATE);
+        }
+        if ((flags&Context.BIND_DEBUG_UNBIND) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEBUG_UNBIND);
+        }
+        if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_FG);
+        }
+        if ((flags&Context.BIND_IMPORTANT_BACKGROUND) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT_BG);
+        }
+        if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ABOVE_CLIENT);
+        }
+        if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ALLOW_OOM_MANAGEMENT);
+        }
+        if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.WAIVE_PRIORITY);
+        }
+        if ((flags&Context.BIND_IMPORTANT) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT);
+        }
+        if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ADJUST_WITH_ACTIVITY);
+        }
+        if ((flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE_WHILE_WAKE);
+        }
+        if ((flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE);
+        }
+        if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.TREAT_LIKE_ACTIVITY);
+        }
+        if ((flags&Context.BIND_VISIBLE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.VISIBLE);
+        }
+        if ((flags&Context.BIND_SHOWING_UI) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.SHOWING_UI);
+        }
+        if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_VISIBLE);
+        }
+        if (serviceDead) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
+        }
+        if (binding.service != null) {
+            proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName);
+        }
+        if (conn != null) {
+            proto.write(ConnectionRecordProto.CONN_HEX_HASH,
+                    Integer.toHexString(System.identityHashCode(conn.asBinder())));
+        }
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index be290e9..01ce64c 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -21,6 +21,10 @@
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
+import com.android.server.am.proto.IntentBindRecordProto;
 
 import java.io.PrintWriter;
 
@@ -106,4 +110,32 @@
         sb.append('}');
         return stringName = sb.toString();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(IntentBindRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        proto.write(IntentBindRecordProto.IS_CREATE,
+                (collectFlags()&Context.BIND_AUTO_CREATE) != 0);
+        if (intent != null) {
+            intent.getIntent().writeToProto(proto,
+                    IntentBindRecordProto.INTENT, false, true, false, false);
+        }
+        if (binder != null) {
+            proto.write(IntentBindRecordProto.BINDER, binder.toString());
+        }
+        proto.write(IntentBindRecordProto.REQUESTED, requested);
+        proto.write(IntentBindRecordProto.RECEIVED, received);
+        proto.write(IntentBindRecordProto.HAS_BOUND, hasBound);
+        proto.write(IntentBindRecordProto.DO_REBIND, doRebind);
+
+        final int N = apps.size();
+        for (int i=0; i<N; i++) {
+            AppBindRecord a = apps.valueAt(i);
+            if (a != null) {
+                a.writeToProto(proto, IntentBindRecordProto.APPS);
+            }
+        }
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 32d03da..8a905f8 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -322,8 +323,7 @@
         return needSep;
     }
 
-    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
-            int opti, boolean dumpAll) {
+    private ArrayList<ContentProviderRecord> getProvidersForName(String name) {
         ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
         ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
 
@@ -365,6 +365,12 @@
                 }
             }
         }
+        return providers;
+    }
+
+    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
 
         if (providers.size() <= 0) {
             return false;
@@ -417,6 +423,33 @@
     }
 
     /**
+     * Similar to the dumpProvider, but only dumps the first matching provider.
+     * The provider is responsible for dumping as proto.
+     */
+    protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+            String[] args) {
+        //add back the --proto arg, which was stripped out by PriorityDump
+        String[] newArgs = Arrays.copyOf(args, args.length + 1);
+        newArgs[args.length] = "--proto";
+
+        ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
+
+        if (providers.size() <= 0) {
+            return false;
+        }
+
+        // Only dump the first provider, since we are dumping in proto format
+        for (int i = 0; i < providers.size(); i++) {
+            final ContentProviderRecord r = providers.get(i);
+            if (r.proc != null && r.proc.thread != null) {
+                dumpToTransferPipe(null, fd, pw, r, newArgs);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider without
      * any meta string (e.g., provider info, indentation) written to the file descriptor.
      */
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 16995e5..b6eff00 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -19,6 +19,7 @@
 import com.android.internal.app.procstats.ServiceState;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.LocalServices;
+import com.android.server.am.proto.ServiceRecordProto;
 import com.android.server.notification.NotificationManagerInternal;
 
 import android.app.INotificationManager;
@@ -42,6 +43,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -79,7 +82,7 @@
     final String permission;// permission needed to access service
     final boolean exported; // from ServiceInfo.exported
     final Runnable restarter; // used to schedule retries of starting the service
-    final long createTime;  // when this service was created
+    final long createRealTime;  // when this service was created
     final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
             = new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
                             // All active bindings to the service.
@@ -103,7 +106,7 @@
     boolean startRequested; // someone explicitly called start?
     boolean delayedStop;    // service has been stopped but is in a delayed start?
     boolean stopIfKilled;   // last onStart() said to stop if service killed?
-    boolean callStart;      // last onStart() has asked to alway be called on restart.
+    boolean callStart;      // last onStart() has asked to always be called on restart.
     int executeNesting;     // number of outstanding operations keeping foreground.
     boolean executeFg;      // should we be executing in the foreground?
     long executingStart;    // start time of last execute request.
@@ -159,6 +162,27 @@
             }
         }
 
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long now) {
+            long token = proto.start(fieldId);
+            proto.write(ServiceRecordProto.StartItemProto.ID, id);
+            ProtoUtils.toDuration(proto,
+                    ServiceRecordProto.StartItemProto.DURATION, deliveredTime, now);
+            proto.write(ServiceRecordProto.StartItemProto.DELIVERY_COUNT, deliveryCount);
+            proto.write(ServiceRecordProto.StartItemProto.DONE_EXECUTING_COUNT, doneExecutingCount);
+            if (intent != null) {
+                intent.writeToProto(proto, ServiceRecordProto.StartItemProto.INTENT, true, true,
+                        true, false);
+            }
+            if (neededGrants != null) {
+                neededGrants.writeToProto(proto, ServiceRecordProto.StartItemProto.NEEDED_GRANTS);
+            }
+            if (uriPermissions != null) {
+                uriPermissions.writeToProto(proto,
+                        ServiceRecordProto.StartItemProto.URI_PERMISSIONS);
+            }
+            proto.end(token);
+        }
+
         public String toString() {
             if (stringName != null) {
                 return stringName;
@@ -209,6 +233,117 @@
         }
     }
 
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(ServiceRecordProto.SHORT_NAME, this.shortName);
+        proto.write(ServiceRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        proto.write(ServiceRecordProto.IS_RUNNING, app != null);
+        if (app != null) {
+            proto.write(ServiceRecordProto.PID, app.pid);
+        }
+        if (intent != null) {
+            intent.getIntent().writeToProto(proto, ServiceRecordProto.INTENT, false, true, false,
+                    true);
+        }
+        proto.write(ServiceRecordProto.PACKAGE_NAME, packageName);
+        proto.write(ServiceRecordProto.PROCESS_NAME, processName);
+        proto.write(ServiceRecordProto.PERMISSION, permission);
+
+        long now = SystemClock.uptimeMillis();
+        long nowReal = SystemClock.elapsedRealtime();
+        if (appInfo != null) {
+            long appInfoToken = proto.start(ServiceRecordProto.APPINFO);
+            proto.write(ServiceRecordProto.AppInfo.BASE_DIR, appInfo.sourceDir);
+            if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+                proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
+            }
+            proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+            proto.end(appInfoToken);
+        }
+        if (app != null) {
+            app.writeToProto(proto, ServiceRecordProto.APP);
+        }
+        if (isolatedProc != null) {
+            isolatedProc.writeToProto(proto, ServiceRecordProto.ISOLATED_PROC);
+        }
+        proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager);
+        proto.write(ServiceRecordProto.DELAYED, delayed);
+        if (isForeground || foregroundId != 0) {
+            long fgToken = proto.start(ServiceRecordProto.FOREGROUND);
+            proto.write(ServiceRecordProto.Foreground.ID, foregroundId);
+            foregroundNoti.writeToProto(proto, ServiceRecordProto.Foreground.NOTIFICATION);
+            proto.end(fgToken);
+        }
+        ProtoUtils.toDuration(proto, ServiceRecordProto.CREATE_REAL_TIME, createRealTime, nowReal);
+        ProtoUtils.toDuration(proto,
+                ServiceRecordProto.STARTING_BG_TIMEOUT, startingBgTimeout, now);
+        ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
+        ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
+        proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+        if (startRequested || delayedStop || lastStartId != 0) {
+            long startToken = proto.start(ServiceRecordProto.START);
+            proto.write(ServiceRecordProto.Start.START_REQUESTED, startRequested);
+            proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
+            proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
+            proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+            proto.end(startToken);
+        }
+
+        if (executeNesting != 0) {
+            long executNestingToken = proto.start(ServiceRecordProto.EXECUTE);
+            proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_NESTING, executeNesting);
+            proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_FG, executeFg);
+            ProtoUtils.toDuration(proto,
+                    ServiceRecordProto.ExecuteNesting.EXECUTING_START, executingStart, now);
+            proto.end(executNestingToken);
+        }
+        if (destroying || destroyTime != 0) {
+            ProtoUtils.toDuration(proto, ServiceRecordProto.DESTORY_TIME, destroyTime, now);
+        }
+        if (crashCount != 0 || restartCount != 0 || restartDelay != 0 || nextRestartTime != 0) {
+            long crashToken = proto.start(ServiceRecordProto.CRASH);
+            proto.write(ServiceRecordProto.Crash.RESTART_COUNT, restartCount);
+            ProtoUtils.toDuration(proto, ServiceRecordProto.Crash.RESTART_DELAY, restartDelay, now);
+            ProtoUtils.toDuration(proto,
+                    ServiceRecordProto.Crash.NEXT_RESTART_TIME, nextRestartTime, now);
+            proto.write(ServiceRecordProto.Crash.CRASH_COUNT, crashCount);
+            proto.end(crashToken);
+        }
+
+        if (deliveredStarts.size() > 0) {
+            final int N = deliveredStarts.size();
+            for (int i = 0; i < N; i++) {
+                deliveredStarts.get(i).writeToProto(proto,
+                        ServiceRecordProto.DELIVERED_STARTS, now);
+            }
+        }
+        if (pendingStarts.size() > 0) {
+            final int N = pendingStarts.size();
+            for (int i = 0; i < N; i++) {
+                pendingStarts.get(i).writeToProto(proto, ServiceRecordProto.PENDING_STARTS, now);
+            }
+        }
+        if (bindings.size() > 0) {
+            final int N = bindings.size();
+            for (int i=0; i<N; i++) {
+                IntentBindRecord b = bindings.valueAt(i);
+                b.writeToProto(proto, ServiceRecordProto.BINDINGS);
+            }
+        }
+        if (connections.size() > 0) {
+            final int N = connections.size();
+            for (int conni=0; conni<N; conni++) {
+                ArrayList<ConnectionRecord> c = connections.valueAt(conni);
+                for (int i=0; i<c.size(); i++) {
+                    c.get(i).writeToProto(proto, ServiceRecordProto.CONNECTIONS);
+                }
+            }
+        }
+        proto.end(token);
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("intent={");
                 pw.print(intent.getIntent().toShortString(false, true, false, true));
@@ -243,7 +378,7 @@
                     pw.print(" foregroundNoti="); pw.println(foregroundNoti);
         }
         pw.print(prefix); pw.print("createTime=");
-                TimeUtils.formatDuration(createTime, nowReal, pw);
+                TimeUtils.formatDuration(createRealTime, nowReal, pw);
                 pw.print(" startingBgTimeout=");
                 TimeUtils.formatDuration(startingBgTimeout, now, pw);
                 pw.println();
@@ -329,7 +464,7 @@
         permission = sInfo.permission;
         exported = sInfo.exported;
         this.restarter = restarter;
-        createTime = SystemClock.elapsedRealtime();
+        createRealTime = SystemClock.elapsedRealtime();
         lastActivity = SystemClock.uptimeMillis();
         userId = UserHandle.getUserId(appInfo.uid);
         createdFromFg = callerIsFg;
diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java
index 28344df..fc07c1a 100644
--- a/services/core/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/am/UriPermissionOwner.java
@@ -20,6 +20,9 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.UriPermissionOwnerProto;
 
 import com.google.android.collect.Sets;
 
@@ -139,6 +142,26 @@
         }
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(UriPermissionOwnerProto.OWNER, owner.toString());
+        if (mReadPerms != null) {
+            synchronized (mReadPerms) {
+                for (UriPermission p : mReadPerms) {
+                    p.uri.writeToProto(proto, UriPermissionOwnerProto.READ_PERMS);
+                }
+            }
+        }
+        if (mWritePerms != null) {
+            synchronized (mWritePerms) {
+                for (UriPermission p : mWritePerms) {
+                    p.uri.writeToProto(proto, UriPermissionOwnerProto.WRITE_PERMS);
+                }
+            }
+        }
+        proto.end(token);
+    }
+
     @Override
     public String toString() {
         return owner.toString();
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 4bdbbe3..e243e56 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetdEventCallback;
+import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.metrics.ConnectStats;
@@ -35,6 +36,7 @@
 import android.util.Log;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -242,13 +244,17 @@
         event.timestampMs = timestampMs;
         event.uid = uid;
         event.ethertype = ethertype;
-        event.dstHwAddr = dstHw;
+        event.dstHwAddr = new MacAddress(dstHw);
         event.srcIp = srcIp;
         event.dstIp = dstIp;
         event.ipNextHeader = ipNextHeader;
         event.srcPort = srcPort;
         event.dstPort = dstPort;
         addWakeupEvent(event);
+
+        String dstMac = event.dstHwAddr.toString();
+        StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED,
+                uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
     }
 
     private void addWakeupEvent(WakeupEvent event) {
diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 0000000..876acf4
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JobService used to persists brightness slider events when the device
+ * is idle and charging.
+ */
+public class BrightnessIdleJob extends JobService {
+
+    // Must be unique within the system server uid.
+    private static final int JOB_ID = 3923512;
+
+    public static void scheduleJob(Context context) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+        JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
+        JobInfo jobInfo =
+                new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
+
+        if (pending != null && !pending.equals(jobInfo)) {
+            jobScheduler.cancel(JOB_ID);
+            pending = null;
+        }
+
+        if (pending == null) {
+            jobScheduler.schedule(jobInfo);
+        }
+    }
+
+    public static void cancelJob(Context context) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        jobScheduler.cancel(JOB_ID);
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        if (BrightnessTracker.DEBUG) {
+            Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
+        }
+        DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
+        dmi.persistBrightnessSliderEvents();
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 361d928..90888f0 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -61,12 +61,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 
 import java.util.Deque;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -75,8 +75,8 @@
  */
 public class BrightnessTracker {
 
-    private static final String TAG = "BrightnessTracker";
-    private static final boolean DEBUG = false;
+    static final String TAG = "BrightnessTracker";
+    static final boolean DEBUG = false;
 
     private static final String EVENTS_FILE = "brightness_events.xml";
     private static final int MAX_EVENTS = 100;
@@ -103,6 +103,8 @@
     @GuardedBy("mEventsLock")
     private RingBuffer<BrightnessChangeEvent> mEvents
             = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+    @GuardedBy("mEventsLock")
+    private boolean mEventsDirty;
     private final Runnable mEventsWriter = () -> writeEvents();
     private volatile boolean mWriteEventsScheduled;
 
@@ -170,6 +172,8 @@
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
         mBroadcastReceiver = new Receiver();
         mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+        mInjector.scheduleIdleJob(mContext);
     }
 
     /** Stop listening for events */
@@ -181,6 +185,7 @@
         mInjector.unregisterSensorListener(mContext, mSensorListener);
         mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
         mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+        mInjector.cancelIdleJob(mContext);
     }
 
     /**
@@ -211,6 +216,10 @@
                 brightness, userId);
     }
 
+    public void persistEvents() {
+        scheduleWriteEvents();
+    }
+
     private void handleBrightnessChanged() {
         if (DEBUG) {
             Slog.d(TAG, "Brightness change");
@@ -278,6 +287,7 @@
             Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
         }
         synchronized (mEventsLock) {
+            mEventsDirty = true;
             mEvents.append(event);
         }
     }
@@ -291,8 +301,12 @@
 
     private void writeEvents() {
         mWriteEventsScheduled = false;
-        // TODO kick off write on handler thread e.g. every 24 hours.
         synchronized (mEventsLock) {
+            if (!mEventsDirty) {
+                // Nothing to write
+                return;
+            }
+
             final AtomicFile writeTo = mInjector.getFile();
             if (writeTo == null) {
                 return;
@@ -301,12 +315,14 @@
                 if (writeTo.exists()) {
                     writeTo.delete();
                 }
+                mEventsDirty = false;
             } else {
                 FileOutputStream output = null;
                 try {
                     output = writeTo.startWrite();
                     writeEventsLocked(output);
                     writeTo.finishWrite(output);
+                    mEventsDirty = false;
                 } catch (IOException e) {
                     writeTo.failWrite(output);
                     Slog.e(TAG, "Failed to write change mEvents.", e);
@@ -317,6 +333,8 @@
 
     private void readEvents() {
         synchronized (mEventsLock) {
+            // Read might prune events so mark as dirty.
+            mEventsDirty = true;
             mEvents.clear();
             final AtomicFile readFrom = mInjector.getFile();
             if (readFrom != null && readFrom.exists()) {
@@ -344,13 +362,16 @@
 
         out.startTag(null, TAG_EVENTS);
         BrightnessChangeEvent[] toWrite = mEvents.toArray();
+        // Clear events, code below will add back the ones that are still within the time window.
+        mEvents.clear();
         if (DEBUG) {
             Slog.d(TAG, "Writing events " + toWrite.length);
         }
-        final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+        final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
         for (int i = 0; i < toWrite.length; ++i) {
             int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
             if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+                mEvents.append(toWrite[i]);
                 out.startTag(null, TAG_EVENT);
                 out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
                 out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +486,17 @@
         }
     }
 
+    public void dump(PrintWriter pw) {
+        synchronized (mEventsLock) {
+            pw.println("BrightnessTracker state:");
+            pw.println("  mEvents.size=" + mEvents.size());
+            pw.println("  mEventsDirty=" + mEventsDirty);
+        }
+        synchronized (mDataCollectionLock) {
+            pw.println("  mLastSensorReadings.size=" + mLastSensorReadings.size());
+        }
+    }
+
     // Not allowed to keep the SensorEvent so used to copy the data we care about.
     private static class LightData {
         public float lux;
@@ -635,5 +667,13 @@
         public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
             return ActivityManager.getService().getFocusedStackInfo();
         }
+
+        public void scheduleIdleJob(Context context) {
+            BrightnessIdleJob.scheduleJob(context);
+        }
+
+        public void cancelIdleJob(Context context) {
+            BrightnessIdleJob.cancelJob(context);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f1e2011..7530f3e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1285,6 +1285,9 @@
 
             pw.println();
             mPersistentDataStore.dump(pw);
+
+            pw.println();
+            mBrightnessTracker.dump(pw);
         }
     }
 
@@ -1921,5 +1924,10 @@
         public boolean isUidPresentOnDisplay(int uid, int displayId) {
             return isUidPresentOnDisplayInternal(uid, displayId);
         }
+
+        @Override
+        public void persistBrightnessSliderEvents() {
+            mBrightnessTracker.persistEvents();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 5e9f355..da481a8 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -18,15 +18,25 @@
 
 import android.Manifest;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.hardware.contexthub.V1_0.AsyncEventType;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HostEndPoint;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.IContexthubCallback;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
 import android.hardware.location.ContextHubInfo;
-import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubMessage;
-import android.hardware.location.IContextHubService;
 import android.hardware.location.IContextHubCallback;
-import android.hardware.location.NanoAppFilter;
+import android.hardware.location.IContextHubService;
+import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoApp;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppFilter;
 import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.NanoAppState;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
@@ -38,8 +48,10 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.HashMap;
 
 /**
  * @hide
@@ -48,55 +60,152 @@
     private static final String TAG = "ContextHubService";
     private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
     private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
-        + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
+            + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
 
-    public static final int ANY_HUB             = -1;
-    public static final int MSG_LOAD_NANO_APP   = 3;
+    /*
+     * Constants for the type of transaction that is defined by ContextHubService.
+     * This is used to report the transaction callback to clients, and is different from
+     * ContextHubTransaction.Type.
+     */
+    public static final int MSG_ENABLE_NANO_APP = 1;
+    public static final int MSG_DISABLE_NANO_APP = 2;
+    public static final int MSG_LOAD_NANO_APP = 3;
     public static final int MSG_UNLOAD_NANO_APP = 4;
+    public static final int MSG_QUERY_NANO_APPS = 5;
+    public static final int MSG_QUERY_MEMORY = 6;
+    public static final int MSG_HUB_RESET = 7;
 
     private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
     private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN;
     private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN;
     private static final int PRE_LOADED_APP_MEM_REQ = 0;
 
-    private static final int MSG_HEADER_SIZE = 4;
-    private static final int HEADER_FIELD_MSG_TYPE = 0;
-    private static final int HEADER_FIELD_MSG_VERSION = 1;
-    private static final int HEADER_FIELD_HUB_HANDLE = 2;
-    private static final int HEADER_FIELD_APP_INSTANCE = 3;
-
-    private static final int HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
-    private static final int HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
-    private static final int MSG_LOAD_APP_HEADER_SIZE = MSG_HEADER_SIZE + 2;
-
     private static final int OS_APP_INSTANCE = -1;
 
     private final Context mContext;
+
+    // TODO(b/69270990): Remove once old ContextHubManager API is deprecated
+    // Service cache maintaining of instance ID to nanoapp infos
     private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash =
             new ConcurrentHashMap<>();
+    // The next available instance ID (managed by the service) to assign to a nanoapp
+    private int mNextAvailableInstanceId = 0;
+    // A map of the long nanoapp ID to instance ID managed by the service
+    private final ConcurrentHashMap<Long, Integer> mNanoAppIdToInstanceMap =
+            new ConcurrentHashMap<>();
+
     private final ContextHubInfo[] mContextHubInfo;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
 
-    private native int nativeSendMessage(int[] header, byte[] data);
-    private native ContextHubInfo[] nativeInitialize();
+    // Proxy object to communicate with the Context Hub HAL
+    private final IContexthub mContextHubProxy;
+
+    // The manager for transaction queue
+    private final ContextHubTransactionManager mTransactionManager;
+
+    /**
+     * Class extending the callback to register with a Context Hub.
+     */
+    private class ContextHubServiceCallback extends IContexthubCallback.Stub {
+        private final int mContextHubId;
+
+        ContextHubServiceCallback(int contextHubId) {
+            mContextHubId = contextHubId;
+        }
+
+        @Override
+        public void handleClientMsg(ContextHubMsg message) {
+            handleClientMessageCallback(mContextHubId, message);
+        }
+
+        @Override
+        public void handleTxnResult(int transactionId, int result) {
+            handleTransactionResultCallback(mContextHubId, transactionId, result);
+        }
+
+        @Override
+        public void handleHubEvent(int eventType) {
+            handleHubEventCallback(mContextHubId, eventType);
+        }
+
+        @Override
+        public void handleAppAbort(long nanoAppId, int abortCode) {
+            handleAppAbortCallback(mContextHubId, nanoAppId, abortCode);
+        }
+
+        @Override
+        public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) {
+            handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
+        }
+    }
 
     public ContextHubService(Context context) {
         mContext = context;
-        mContextHubInfo = nativeInitialize();
+
+        mContextHubProxy = getContextHubProxy();
+        if (mContextHubProxy == null) {
+            mTransactionManager = null;
+            mContextHubInfo = new ContextHubInfo[0];
+            return;
+        }
+
+        mTransactionManager = new ContextHubTransactionManager(mContextHubProxy);
+
+        List<ContextHub> hubList;
+        try {
+            hubList = mContextHubProxy.getHubs();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while getting Context Hub info");
+            hubList = Collections.emptyList();
+        }
+        mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+
+        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+            int contextHubId = contextHubInfo.getId();
+            try {
+                mContextHubProxy.registerCallback(
+                        contextHubId, new ContextHubServiceCallback(contextHubId));
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
+                        + contextHubId + ")");
+            }
+        }
+
+        // Do a query to initialize the service cache list of nanoapps
+        // TODO(b/69270990): Remove this when old API is deprecated
+        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+            queryNanoAppsInternal(contextHubInfo.getId());
+        }
 
         for (int i = 0; i < mContextHubInfo.length; i++) {
             Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
-                  + ", name:  " + mContextHubInfo[i].getName());
+                    + ", name:  " + mContextHubInfo[i].getName());
         }
     }
 
+    /**
+     * @return the IContexthub proxy interface
+     */
+    private IContexthub getContextHubProxy() {
+        IContexthub proxy = null;
+        try {
+            proxy = IContexthub.getService(true /* retry */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy");
+        } catch (NoSuchElementException e) {
+            Log.i(TAG, "Context Hub HAL service not found");
+        }
+
+        return proxy;
+    }
+
     @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
         checkPermissions();
         mCallbacksList.register(callback);
         Log.d(TAG, "Added callback, total callbacks " +
-              mCallbacksList.getRegisteredCallbackCount());
+                mCallbacksList.getRegisteredCallbackCount());
         return 0;
     }
 
@@ -109,29 +218,112 @@
         for (int i = 0; i < returnArray.length; ++i) {
             returnArray[i] = i;
             Log.d(TAG, String.format("Hub %s is mapped to %d",
-                                     mContextHubInfo[i].getName(), returnArray[i]));
+                    mContextHubInfo[i].getName(), returnArray[i]));
         }
 
         return returnArray;
     }
 
     @Override
-    public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+    public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
         checkPermissions();
-        if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid context hub handle " + contextHubHandle);
+        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+            Log.e(TAG, "Invalid context hub handle " + contextHubId);
             return null; // null means fail
         }
 
-        return mContextHubInfo[contextHubHandle];
+        return mContextHubInfo[contextHubId];
+    }
+
+    /**
+     * Creates an internal load transaction callback to be used for old API clients
+     *
+     * @param contextHubId  the ID of the hub to load the binary
+     * @param nanoAppBinary the binary to load
+     * @return the callback interface
+     */
+    private IContextHubTransactionCallback createLoadTransactionCallback(
+            int contextHubId, NanoAppBinary nanoAppBinary) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+                handleLoadResponseOldApi(contextHubId, result, nanoAppBinary);
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+            }
+        };
+    }
+
+    /**
+     * Creates an internal unload transaction callback to be used for old API clients
+     *
+     * @param contextHubId the ID of the hub to unload the nanoapp
+     * @param nanoAppId    the ID of the nanoapp to unload
+     * @return the callback interface
+     */
+    private IContextHubTransactionCallback createUnloadTransactionCallback(
+            int contextHubId, long nanoAppId) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+                handleUnloadResponseOldApi(contextHubId, result, nanoAppId);
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+            }
+        };
+    }
+
+    /**
+     * Creates an internal query transaction callback to be used for old API clients
+     *
+     * @param contextHubId the ID of the hub to query
+     * @return the callback interface
+     */
+    private IContextHubTransactionCallback createQueryTransactionCallback(int contextHubId) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                byte[] data = {(byte) result};
+                onMessageReceipt(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
+            }
+        };
+    }
+
+    /**
+     * Adds a new transaction to the transaction manager queue
+     *
+     * @param transaction the transaction to add
+     * @return the result of adding the transaction
+     */
+    private int addTransaction(ContextHubServiceTransaction transaction) {
+        int result = Result.OK;
+        try {
+            mTransactionManager.addTransaction(transaction);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, e.getMessage());
+            result = Result.TRANSACTION_PENDING; /* failed */
+        }
+
+        return result;
     }
 
     @Override
-    public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException {
+    public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
         checkPermissions();
+        if (mContextHubProxy == null) {
+            return -1;
+        }
 
-        if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle);
+        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+            Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
             return -1;
         }
         if (app == null) {
@@ -139,20 +331,17 @@
             return -1;
         }
 
-        int[] msgHeader = new int[MSG_LOAD_APP_HEADER_SIZE];
-        msgHeader[HEADER_FIELD_HUB_HANDLE] = contextHubHandle;
-        msgHeader[HEADER_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
-        msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
-        msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP;
+        // Create an internal IContextHubTransactionCallback for the old API clients
+        NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+        IContextHubTransactionCallback onCompleteCallback =
+                createLoadTransactionCallback(contextHubId, nanoAppBinary);
 
-        long appId = app.getAppId();
+        ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
+                contextHubId, nanoAppBinary, onCompleteCallback);
 
-        msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF);
-        msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF);
-
-        int errVal = nativeSendMessage(msgHeader, app.getAppBinary());
-        if (errVal != 0) {
-            Log.e(TAG, "Send Message returns error" + contextHubHandle);
+        int result = addTransaction(transaction);
+        if (result != Result.OK) {
+            Log.e(TAG, "Failed to load nanoapp with error code " + result);
             return -1;
         }
 
@@ -163,23 +352,26 @@
     @Override
     public int unloadNanoApp(int nanoAppInstanceHandle) throws RemoteException {
         checkPermissions();
+        if (mContextHubProxy == null) {
+            return -1;
+        }
+
         NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstanceHandle);
         if (info == null) {
             Log.e(TAG, "Cannot find app with handle " + nanoAppInstanceHandle);
             return -1; //means failed
         }
 
-        // Call Native interface here
-        int[] msgHeader = new int[MSG_HEADER_SIZE];
-        msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB;
-        msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppInstanceHandle;
-        msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
-        msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_UNLOAD_NANO_APP;
+        int contextHubId = info.getContexthubId();
+        long nanoAppId = info.getAppId();
+        IContextHubTransactionCallback onCompleteCallback =
+                createUnloadTransactionCallback(contextHubId, nanoAppId);
+        ContextHubServiceTransaction transaction = mTransactionManager.createUnloadTransaction(
+                contextHubId, nanoAppId, onCompleteCallback);
 
-        byte msg[] = new byte[0];
-
-        if (nativeSendMessage(msgHeader, msg) != 0) {
-            Log.e(TAG, "native send message fails");
+        int result = addTransaction(transaction);
+        if (result != Result.OK) {
+            Log.e(TAG, "Failed to unload nanoapp with error code " + result);
             return -1;
         }
 
@@ -189,7 +381,7 @@
 
     @Override
     public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle)
-                                                      throws RemoteException {
+            throws RemoteException {
         checkPermissions();
         // This assumes that all the nanoAppInfo is current. This is reasonable
         // for the use cases for tightly controlled nanoApps.
@@ -206,7 +398,7 @@
         checkPermissions();
         ArrayList<Integer> foundInstances = new ArrayList<Integer>();
 
-        for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+        for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
             NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
 
             if (filter.testMatch(info)) {
@@ -223,23 +415,230 @@
         return retArray;
     }
 
-    @Override
-    public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
-                           throws RemoteException {
-        checkPermissions();
+    /**
+     * Performs a query at the specified hub.
+     *
+     * This method should only be invoked internally by the service, either to update the service
+     * cache or as a result of an explicit query requested by a client through the sendMessage API.
+     *
+     * @param contextHubId the ID of the hub to do the query
+     * @return the result of the query
+     */
+    private int queryNanoAppsInternal(int contextHubId) {
+        if (mContextHubProxy == null) {
+            return Result.UNKNOWN_FAILURE;
+        }
 
-        if (msg == null || msg.getData() == null) {
-            Log.w(TAG, "null ptr");
+        IContextHubTransactionCallback onCompleteCallback =
+                createQueryTransactionCallback(contextHubId);
+        ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+                contextHubId, onCompleteCallback);
+
+        return addTransaction(transaction);
+    }
+
+    @Override
+    public int sendMessage(
+            int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+        checkPermissions();
+        if (mContextHubProxy == null) {
+            return -1;
+        }
+        if (msg == null) {
+            Log.e(TAG, "ContextHubMessage cannot be null");
+            return -1;
+        }
+        if (msg.getData() == null) {
+            Log.w(TAG, "ContextHubMessage message body cannot be null");
             return -1;
         }
 
-        int[] msgHeader = new int[MSG_HEADER_SIZE];
-        msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle;
-        msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppHandle;
-        msgHeader[HEADER_FIELD_MSG_VERSION] = msg.getVersion();
-        msgHeader[HEADER_FIELD_MSG_TYPE] = msg.getMsgType();
+        int result;
+        if (nanoAppHandle == OS_APP_INSTANCE) {
+            if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
+                result = queryNanoAppsInternal(hubHandle);
+            } else {
+                Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
+                result = Result.BAD_PARAMS;
+            }
+        } else {
+            NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle);
+            if (info != null) {
+                ContextHubMsg hubMessage = new ContextHubMsg();
+                hubMessage.appName = info.getAppId();
+                hubMessage.msgType = msg.getMsgType();
+                hubMessage.hostEndPoint = HostEndPoint.UNSPECIFIED;
+                ContextHubServiceUtil.copyToByteArrayList(msg.getData(), hubMessage.msg);
 
-        return nativeSendMessage(msgHeader, msg.getData());
+                try {
+                    result = mContextHubProxy.sendMessageToHub(hubHandle, hubMessage);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to send nanoapp message - RemoteException");
+                    result = Result.UNKNOWN_FAILURE;
+                }
+            } else {
+                Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID "
+                        + nanoAppHandle + " does not exist.");
+                result = Result.BAD_PARAMS;
+            }
+        }
+
+        return (result == Result.OK ? 0 : -1);
+    }
+
+    /**
+     * Handles a unicast or broadcast message from a nanoapp.
+     *
+     * @param contextHubId the ID of the hub the message came from
+     * @param message      the message contents
+     */
+    private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
+        // TODO(b/67734082): Send to new API clients
+        byte[] data = ContextHubServiceUtil.createPrimitiveByteArray(message.msg);
+
+        int nanoAppInstanceId = mNanoAppIdToInstanceMap.containsKey(message.appName) ?
+                mNanoAppIdToInstanceMap.get(message.appName) : -1;
+        onMessageReceipt(message.msgType, contextHubId, nanoAppInstanceId, data);
+    }
+
+    /**
+     * A helper function to handle a load response from the Context Hub for the old API.
+     *
+     * TODO(b/69270990): Remove this once the old APIs are obsolete.
+     */
+    private void handleLoadResponseOldApi(
+            int contextHubId, int result, NanoAppBinary nanoAppBinary) {
+        if (nanoAppBinary == null) {
+            Log.e(TAG, "Nanoapp binary field was null for a load transaction");
+            return;
+        }
+
+        // NOTE: The legacy JNI code used to do a query right after a load success
+        // to synchronize the service cache. Instead store the binary that was requested to
+        // load to update the cache later without doing a query.
+        int instanceId = 0;
+        long nanoAppId = nanoAppBinary.getNanoAppId();
+        int nanoAppVersion = nanoAppBinary.getNanoAppVersion();
+        if (result == TransactionResult.SUCCESS) {
+            if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+                instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+            } else {
+                instanceId = mNextAvailableInstanceId++;
+                mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+            }
+
+            addAppInstance(contextHubId, instanceId, nanoAppId, nanoAppVersion);
+        }
+
+        byte[] data = new byte[5];
+        data[0] = (byte) result;
+        ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId);
+
+        onMessageReceipt(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+    }
+
+    /**
+     * A helper function to handle an unload response from the Context Hub for the old API.
+     *
+     * TODO(b/69270990): Remove this once the old APIs are obsolete.
+     */
+    private void handleUnloadResponseOldApi(
+            int contextHubId, int result, long nanoAppId) {
+        if (result == TransactionResult.SUCCESS) {
+            int instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+            deleteAppInstance(instanceId);
+            mNanoAppIdToInstanceMap.remove(nanoAppId);
+        }
+
+        byte[] data = new byte[1];
+        data[0] = (byte) result;
+        onMessageReceipt(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+    }
+
+    /**
+     * Handles a transaction response from a Context Hub.
+     *
+     * @param contextHubId  the ID of the hub the response came from
+     * @param transactionId the ID of the transaction
+     * @param result        the result of the transaction reported by the hub
+     */
+    private void handleTransactionResultCallback(int contextHubId, int transactionId, int result) {
+        mTransactionManager.onTransactionResponse(transactionId, result);
+    }
+
+    /**
+     * Handles an asynchronous event from a Context Hub.
+     *
+     * @param contextHubId the ID of the hub the response came from
+     * @param eventType    the type of the event as defined in Context Hub HAL AsyncEventType
+     */
+    private void handleHubEventCallback(int contextHubId, int eventType) {
+        if (eventType == AsyncEventType.RESTARTED) {
+            mTransactionManager.onHubReset();
+            queryNanoAppsInternal(contextHubId);
+
+            byte[] data = {TransactionResult.SUCCESS};
+            onMessageReceipt(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+        } else {
+            Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = "
+                    + eventType + ")");
+        }
+    }
+
+    /**
+     * Handles an asynchronous abort event of a nanoapp.
+     *
+     * @param contextHubId the ID of the hub that the nanoapp aborted in
+     * @param nanoAppId    the ID of the aborted nanoapp
+     * @param abortCode    the nanoapp-specific abort code
+     */
+    private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) {
+        // TODO(b/31049861): Implement this
+    }
+
+    /**
+     * Handles a query response from a Context Hub.
+     *
+     * @param contextHubId    the ID of the hub of the response
+     * @param nanoAppInfoList the list of loaded nanoapps
+     */
+    private void handleQueryAppsCallback(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+        List<NanoAppState> nanoAppStateList =
+                ContextHubServiceUtil.createNanoAppStateList(nanoAppInfoList);
+
+        updateServiceCache(contextHubId, nanoAppInfoList);
+        mTransactionManager.onQueryResponse(nanoAppStateList);
+    }
+
+    /**
+     * Updates the service's cache of the list of loaded nanoapps using a nanoapp list response.
+     *
+     * TODO(b/69270990): Remove this when the old API functionality is removed.
+     *
+     * @param contextHubId    the ID of the hub the response came from
+     * @param nanoAppInfoList the list of loaded nanoapps
+     */
+    private void updateServiceCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+        synchronized (mNanoAppHash) {
+            for (int instanceId : mNanoAppHash.keySet()) {
+                if (mNanoAppHash.get(instanceId).getContexthubId() == contextHubId) {
+                    deleteAppInstance(instanceId);
+                }
+            }
+
+            for (HubAppInfo appInfo : nanoAppInfoList) {
+                int instanceId;
+                long nanoAppId = appInfo.appId;
+                if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+                    instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+                } else {
+                    instanceId = mNextAvailableInstanceId++;
+                    mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+                }
+
+                addAppInstance(contextHubId, instanceId, nanoAppId, appInfo.version);
+            }
+        }
     }
 
     @Override
@@ -257,7 +656,7 @@
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
-        for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+        for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
             pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString());
         }
 
@@ -268,19 +667,15 @@
         mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
     }
 
-    private int onMessageReceipt(int[] header, byte[] data) {
-        if (header == null || data == null || header.length < MSG_HEADER_SIZE) {
-            return  -1;
+    private int onMessageReceipt(int msgType, int hubHandle, int appInstance, byte[] data) {
+        if (data == null) {
+            return -1;
         }
 
+        int msgVersion = 0;
         int callbacksCount = mCallbacksList.beginBroadcast();
-        int msgType = header[HEADER_FIELD_MSG_TYPE];
-        int msgVersion = header[HEADER_FIELD_MSG_VERSION];
-        int hubHandle = header[HEADER_FIELD_HUB_HANDLE];
-        int appInstance = header[HEADER_FIELD_APP_INSTANCE];
-
         Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
-              hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+                hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
 
         if (callbacksCount < 1) {
             Log.v(TAG, "No message callbacks registered.");
@@ -323,8 +718,8 @@
         }
 
         mNanoAppHash.put(appInstanceHandle, appInfo);
-        Log.d(TAG, action + " app instance " + appInstanceHandle + " with id "
-              + appId + " version " + appVersion);
+        Log.d(TAG, action + " app instance " + appInstanceHandle + " with id 0x"
+                + Long.toHexString(appId) + " version 0x" + Integer.toHexString(appVersion));
 
         return 0;
     }
diff --git a/services/core/java/com/android/server/location/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/ContextHubServiceTransaction.java
new file mode 100644
index 0000000..66145bb
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubServiceTransaction.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppState;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An abstract class representing transactions requested to the Context Hub Service.
+ *
+ * @hide
+ */
+/* package */ abstract class ContextHubServiceTransaction {
+    private final int mTransactionId;
+    @ContextHubTransaction.Type
+    private final int mTransactionType;
+
+    /*
+     * true if the transaction has already completed, false otherwise
+     */
+    private boolean mIsComplete = false;
+
+    /* package */ ContextHubServiceTransaction(int id, int type) {
+        mTransactionId = id;
+        mTransactionType = type;
+    }
+
+    /**
+     * Starts this transaction with a Context Hub.
+     *
+     * All instances of this class must implement this method by making an asynchronous request to
+     * a hub.
+     *
+     * @return the synchronous error code of the transaction start
+     */
+    /* package */
+    abstract int onTransact();
+
+    /**
+     * A function to invoke when a transaction times out.
+     *
+     * All instances of this class must implement this method by reporting the timeout to the
+     * client.
+     */
+    /* package */
+    abstract void onTimeout();
+
+    /**
+     * A function to invoke when the transaction completes.
+     *
+     * Only relevant for load, unload, enable, or disable transactions.
+     *
+     * @param result the result of the transaction
+     */
+    /* package */ void onTransactionComplete(int result) {
+    }
+
+    /**
+     * A function to invoke when a query transaction completes.
+     *
+     * Only relevant for query transactions.
+     *
+     * @param result           the result of the query
+     * @param nanoAppStateList the list of nanoapps given by the query response
+     */
+    /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+    }
+
+    /**
+     * @return the ID of this transaction
+     */
+    /* package */ int getTransactionId() {
+        return mTransactionId;
+    }
+
+    /**
+     * @return the type of this transaction
+     * @see ContextHubTransaction.Type
+     */
+    @ContextHubTransaction.Type
+    /* package */ int getTransactionType() {
+        return mTransactionType;
+    }
+
+    /**
+     * Gets the timeout period as defined in IContexthub.hal
+     *
+     * @return the timeout of this transaction in the specified time unit
+     */
+    /* package */ long getTimeout(TimeUnit unit) {
+        switch (mTransactionType) {
+            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+                return unit.convert(30L, TimeUnit.SECONDS);
+            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+            case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+                // Note: query timeout is not specified at the HAL
+            default: /* fall through */
+                return unit.convert(5L, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Marks the transaction as complete.
+     *
+     * Should only be called as a result of a response from a Context Hub callback
+     */
+    /* package */ void setComplete() {
+        mIsComplete = true;
+    }
+
+    /**
+     * @return true if the transaction has already completed, false otherwise
+     */
+    /* package */ boolean isComplete() {
+        return mIsComplete;
+    }
+
+    /**
+     * @return the human-readable string of this transaction's type
+     */
+    private String getTransactionTypeString() {
+        switch (mTransactionType) {
+            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+                return "Load";
+            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+                return "Unload";
+            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+                return "Enable";
+            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+                return "Disable";
+            case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+                return "Query";
+            default:
+                return "Unknown";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getTransactionTypeString() + " transaction (ID = " + mTransactionId + ")";
+    }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
new file mode 100644
index 0000000..ddbaf86
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HostEndPoint;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * A class encapsulating helper functions used by the ContextHubService class
+ */
+/* package */ class ContextHubServiceUtil {
+    private static final String TAG = "ContextHubServiceUtil";
+
+    /**
+     * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+     *
+     * @param hubList the ContextHub ArrayList
+     * @return the ContextHubInfo array
+     */
+    /* package */
+    static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
+        ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
+        for (int i = 0; i < hubList.size(); i++) {
+            contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+        }
+
+        return contextHubInfoList;
+    }
+
+    /**
+     * Copies a primitive byte array to a ArrayList<Byte>.
+     *
+     * @param inputArray  the primitive byte array
+     * @param outputArray the ArrayList<Byte> array to append
+     */
+    /* package */
+    static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
+        outputArray.clear();
+        outputArray.ensureCapacity(inputArray.length);
+        for (byte element : inputArray) {
+            outputArray.add(element);
+        }
+    }
+
+    /**
+     * Creates a byte array given a ArrayList<Byte> and copies its contents.
+     *
+     * @param array the ArrayList<Byte> object
+     * @return the byte array
+     */
+    /* package */
+    static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
+        byte[] primitiveArray = new byte[array.size()];
+        for (int i = 0; i < array.size(); i++) {
+            primitiveArray[i] = array.get(i);
+        }
+
+        return primitiveArray;
+    }
+
+    /**
+     * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
+     * android.hardware.location.NanoAppBinary object.
+     *
+     * @param nanoAppBinary the client-facing NanoAppBinary object
+     * @return the Context Hub HAL's NanoAppBinary object
+     */
+    /* package */
+    static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
+            NanoAppBinary nanoAppBinary) {
+        android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+                new android.hardware.contexthub.V1_0.NanoAppBinary();
+
+        hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
+        hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
+        hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
+        hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
+        hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();
+
+        // Log exceptions while processing the binary, but continue to pass down the binary
+        // since the error checking is deferred to the Context Hub.
+        try {
+            copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
+        } catch (IndexOutOfBoundsException e) {
+            Log.w(TAG, e.getMessage());
+        } catch (NullPointerException e) {
+            Log.w(TAG, "NanoApp binary was null");
+        }
+
+        return hidlNanoAppBinary;
+    }
+
+    /**
+     * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
+     *
+     * @param nanoAppInfoList the array of HubAppInfo objects
+     * @return the corresponding array of NanoAppState objects
+     */
+    /* package */
+    static List<NanoAppState> createNanoAppStateList(
+            List<HubAppInfo> nanoAppInfoList) {
+        ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
+        for (HubAppInfo appInfo : nanoAppInfoList) {
+            nanoAppStateList.add(
+                    new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
+        }
+
+        return nanoAppStateList;
+    }
+
+    /**
+     * Creates a HIDL ContextHubMsg object to send to a nanoapp.
+     *
+     * @param hostEndPoint the ID of the client sending the message
+     * @param message      the client-facing NanoAppMessage object describing the message
+     * @return the HIDL ContextHubMsg object
+     */
+    /* package */
+    static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) {
+        ContextHubMsg hidlMessage = new ContextHubMsg();
+
+        hidlMessage.appName = message.getNanoAppId();
+        hidlMessage.hostEndPoint = hostEndPoint;
+        hidlMessage.msgType = message.getMessageType();
+        copyToByteArrayList(message.getMessageBody(), hidlMessage.msg);
+
+        return hidlMessage;
+    }
+
+    /**
+     * Creates a client-facing NanoAppMessage object to send to a client.
+     *
+     * @param message the HIDL ContextHubMsg object from a nanoapp
+     * @return the NanoAppMessage object
+     */
+    /* package */
+    static NanoAppMessage createNanoAppMessage(ContextHubMsg message) {
+        byte[] messageArray = createPrimitiveByteArray(message.msg);
+
+        return NanoAppMessage.createMessageFromNanoApp(
+                message.appName, message.msgType, messageArray,
+                message.hostEndPoint == HostEndPoint.BROADCAST);
+    }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
new file mode 100644
index 0000000..898b76c
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppState;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages transactions at the Context Hub Service.
+ *
+ * This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ * and executes them through the Context Hub. At any point in time, either the transaction queue is
+ * empty, or there is a pending transaction that is waiting for an asynchronous response from the
+ * hub. This class also handles synchronous errors and timeouts of each transaction.
+ *
+ * @hide
+ */
+/* package */ class ContextHubTransactionManager {
+    private static final String TAG = "ContextHubTransactionManager";
+
+    /*
+     * Maximum number of transaction requests that can be pending at a time
+     */
+    private static final int MAX_PENDING_REQUESTS = 10;
+
+    /*
+     * The proxy to talk to the Context Hub
+     */
+    private final IContexthub mContextHubProxy;
+
+    /*
+     * A queue containing the current transactions
+     */
+    private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
+
+    /*
+     * The next available transaction ID
+     */
+    private final AtomicInteger mNextAvailableId = new AtomicInteger();
+
+    /*
+     * An executor and the future object for scheduling timeout timers
+     */
+    private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
+    private ScheduledFuture<?> mTimeoutFuture = null;
+
+    /* package */ ContextHubTransactionManager(IContexthub contextHubProxy) {
+        mContextHubProxy = contextHubProxy;
+    }
+
+    /**
+     * Creates a transaction for loading a nanoapp.
+     *
+     * @param contextHubId       the ID of the hub to load the nanoapp to
+     * @param nanoAppBinary      the binary of the nanoapp to load
+     * @param onCompleteCallback the client on complete callback
+     * @return the generated transaction
+     */
+    /* package */ ContextHubServiceTransaction createLoadTransaction(
+            int contextHubId, NanoAppBinary nanoAppBinary,
+            IContextHubTransactionCallback onCompleteCallback) {
+        return new ContextHubServiceTransaction(
+                mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
+            @Override
+            /* package */ int onTransact() {
+                android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+                        ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
+                try {
+                    return mContextHubProxy.loadNanoApp(
+                            contextHubId, hidlNanoAppBinary, this.getTransactionId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
+                            Long.toHexString(nanoAppBinary.getNanoAppId()));
+                    return Result.UNKNOWN_FAILURE;
+                }
+            }
+
+            @Override
+            /* package */ void onTimeout() {
+                onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+            }
+
+            @Override
+            /* package */ void onTransactionComplete(int result) {
+                try {
+                    onCompleteCallback.onTransactionComplete(result);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+                }
+            }
+        };
+    }
+
+    /**
+     * Creates a transaction for unloading a nanoapp.
+     *
+     * @param contextHubId       the ID of the hub to load the nanoapp to
+     * @param nanoAppId          the ID of the nanoapp to unload
+     * @param onCompleteCallback the client on complete callback
+     * @return the generated transaction
+     */
+    /* package */ ContextHubServiceTransaction createUnloadTransaction(
+            int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
+        return new ContextHubServiceTransaction(
+                mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
+            @Override
+            /* package */ int onTransact() {
+                try {
+                    return mContextHubProxy.unloadNanoApp(
+                            contextHubId, nanoAppId, this.getTransactionId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
+                            Long.toHexString(nanoAppId));
+                    return Result.UNKNOWN_FAILURE;
+                }
+            }
+
+            @Override
+            /* package */ void onTimeout() {
+                onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+            }
+
+            @Override
+            /* package */ void onTransactionComplete(int result) {
+                try {
+                    onCompleteCallback.onTransactionComplete(result);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+                }
+            }
+        };
+    }
+
+    /**
+     * Creates a transaction for querying for a list of nanoapps.
+     *
+     * @param contextHubId       the ID of the hub to query
+     * @param onCompleteCallback the client on complete callback
+     * @return the generated transaction
+     */
+    /* package */ ContextHubServiceTransaction createQueryTransaction(
+            int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
+        return new ContextHubServiceTransaction(
+                mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+            @Override
+            /* package */ int onTransact() {
+                try {
+                    return mContextHubProxy.queryApps(contextHubId);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to query for nanoapps");
+                    return Result.UNKNOWN_FAILURE;
+                }
+            }
+
+            @Override
+            /* package */ void onTimeout() {
+                onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
+                        Collections.emptyList());
+            }
+
+            @Override
+            /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                try {
+                    onCompleteCallback.onQueryResponse(result, nanoAppStateList);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onQueryComplete");
+                }
+            }
+        };
+    }
+
+    /**
+     * Adds a new transaction to the queue.
+     *
+     * If there was no pending transaction at the time, the transaction that was added will be
+     * started in this method.
+     *
+     * @param transaction the transaction to add
+     * @throws IllegalStateException if the queue is full
+     */
+    /* package */
+    synchronized void addTransaction(
+            ContextHubServiceTransaction transaction) throws IllegalStateException {
+        if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
+            throw new IllegalStateException("Transaction transaction queue is full (capacity = "
+                    + MAX_PENDING_REQUESTS + ")");
+        }
+        mTransactionQueue.add(transaction);
+
+        if (mTransactionQueue.size() == 1) {
+            startNextTransaction();
+        }
+    }
+
+    /**
+     * Handles a transaction response from a Context Hub.
+     *
+     * @param transactionId the transaction ID of the response
+     * @param result        the result of the transaction
+     */
+    /* package */
+    synchronized void onTransactionResponse(int transactionId, int result) {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+            return;
+        }
+        if (transaction.getTransactionId() != transactionId) {
+            Log.w(TAG, "Received unexpected transaction response (expected ID = "
+                    + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
+            return;
+        }
+
+        transaction.onTransactionComplete(result);
+        removeTransactionAndStartNext();
+    }
+
+    /**
+     * Handles a query response from a Context Hub.
+     *
+     * @param nanoAppStateList the list of nanoapps included in the response
+     */
+    /* package */
+    synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            Log.w(TAG, "Received unexpected query response (no transaction pending)");
+            return;
+        }
+        if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+            Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+            return;
+        }
+
+        transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList);
+        removeTransactionAndStartNext();
+    }
+
+    /**
+     * Handles a hub reset event by stopping a pending transaction and starting the next.
+     */
+    /* package */
+    synchronized void onHubReset() {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            return;
+        }
+
+        removeTransactionAndStartNext();
+    }
+
+    /**
+     * Pops the front transaction from the queue and starts the next pending transaction request.
+     *
+     * Removing elements from the transaction queue must only be done through this method. When a
+     * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
+     * complete.
+     *
+     * It is assumed that the transaction queue is non-empty when this method is invoked, and that
+     * the caller has obtained a lock on this ContextHubTransactionManager object.
+     */
+    private void removeTransactionAndStartNext() {
+        mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
+
+        ContextHubServiceTransaction transaction = mTransactionQueue.remove();
+        transaction.setComplete();
+
+        if (!mTransactionQueue.isEmpty()) {
+            startNextTransaction();
+        }
+    }
+
+    /**
+     * Starts the next pending transaction request.
+     *
+     * Starting new transactions must only be done through this method. This method continues to
+     * process the transaction queue as long as there are pending requests, and no transaction is
+     * pending.
+     *
+     * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
+     * object.
+     */
+    private void startNextTransaction() {
+        int result = Result.UNKNOWN_FAILURE;
+        while (result != Result.OK && !mTransactionQueue.isEmpty()) {
+            ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+            result = transaction.onTransact();
+
+            if (result == Result.OK) {
+                Runnable onTimeoutFunc = () -> {
+                    synchronized (this) {
+                        if (!transaction.isComplete()) {
+                            Log.d(TAG, transaction + " timed out");
+                            transaction.onTimeout();
+
+                            removeTransactionAndStartNext();
+                        }
+                    }
+                };
+
+                long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
+                mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
+                        TimeUnit.SECONDS);
+            } else {
+                mTransactionQueue.remove();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a1a0106..94a4dc8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -603,160 +603,156 @@
     }
 
     private void migrateOldData() {
-        try {
-            // These Settings moved before multi-user was enabled, so we only have to do it for the
-            // root user.
-            if (getString("migrated", null, 0) == null) {
-                final ContentResolver cr = mContext.getContentResolver();
-                for (String validSetting : VALID_SETTINGS) {
-                    String value = Settings.Secure.getString(cr, validSetting);
-                    if (value != null) {
-                        setString(validSetting, value, 0);
-                    }
+        // These Settings moved before multi-user was enabled, so we only have to do it for the
+        // root user.
+        if (getString("migrated", null, 0) == null) {
+            final ContentResolver cr = mContext.getContentResolver();
+            for (String validSetting : VALID_SETTINGS) {
+                String value = Settings.Secure.getString(cr, validSetting);
+                if (value != null) {
+                    setString(validSetting, value, 0);
                 }
-                // No need to move the password / pattern files. They're already in the right place.
-                setString("migrated", "true", 0);
-                Slog.i(TAG, "Migrated lock settings to new location");
             }
+            // No need to move the password / pattern files. They're already in the right place.
+            setString("migrated", "true", 0);
+            Slog.i(TAG, "Migrated lock settings to new location");
+        }
 
-            // These Settings changed after multi-user was enabled, hence need to be moved per user.
-            if (getString("migrated_user_specific", null, 0) == null) {
-                final ContentResolver cr = mContext.getContentResolver();
-                List<UserInfo> users = mUserManager.getUsers();
-                for (int user = 0; user < users.size(); user++) {
-                    // Migrate owner info
-                    final int userId = users.get(user).id;
-                    final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
-                    String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
-                    if (!TextUtils.isEmpty(ownerInfo)) {
-                        setString(OWNER_INFO, ownerInfo, userId);
-                        Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
-                    }
-
-                    // Migrate owner info enabled. Note there was a bug where older platforms only
-                    // stored this value if the checkbox was toggled at least once. The code detects
-                    // this case by handling the exception.
-                    final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
-                    boolean enabled;
-                    try {
-                        int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
-                        enabled = ivalue != 0;
-                        setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
-                    } catch (SettingNotFoundException e) {
-                        // Setting was never stored. Store it if the string is not empty.
-                        if (!TextUtils.isEmpty(ownerInfo)) {
-                            setLong(OWNER_INFO_ENABLED, 1, userId);
-                        }
-                    }
-                    Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
-                }
-                // No need to move the password / pattern files. They're already in the right place.
-                setString("migrated_user_specific", "true", 0);
-                Slog.i(TAG, "Migrated per-user lock settings to new location");
-            }
-
-            // Migrates biometric weak such that the fallback mechanism becomes the primary.
-            if (getString("migrated_biometric_weak", null, 0) == null) {
-                List<UserInfo> users = mUserManager.getUsers();
-                for (int i = 0; i < users.size(); i++) {
-                    int userId = users.get(i).id;
-                    long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                            userId);
-                    long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                            userId);
-                    if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
-                        setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                                alternateType,
-                                userId);
-                    }
-                    setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                            userId);
-                }
-                setString("migrated_biometric_weak", "true", 0);
-                Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
-            }
-
-            // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
-            // user was present on the system, so if we're upgrading to M and there is more than one
-            // user we disable the flag to remain consistent.
-            if (getString("migrated_lockscreen_disabled", null, 0) == null) {
-                final List<UserInfo> users = mUserManager.getUsers();
-                final int userCount = users.size();
-                int switchableUsers = 0;
-                for (int i = 0; i < userCount; i++) {
-                    if (users.get(i).supportsSwitchTo()) {
-                        switchableUsers++;
-                    }
+        // These Settings changed after multi-user was enabled, hence need to be moved per user.
+        if (getString("migrated_user_specific", null, 0) == null) {
+            final ContentResolver cr = mContext.getContentResolver();
+            List<UserInfo> users = mUserManager.getUsers();
+            for (int user = 0; user < users.size(); user++) {
+                // Migrate owner info
+                final int userId = users.get(user).id;
+                final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+                String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+                if (!TextUtils.isEmpty(ownerInfo)) {
+                    setString(OWNER_INFO, ownerInfo, userId);
+                    Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
                 }
 
-                if (switchableUsers > 1) {
-                    for (int i = 0; i < userCount; i++) {
-                        int id = users.get(i).id;
-
-                        if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
-                            setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
-                        }
-                    }
-                }
-
-                setString("migrated_lockscreen_disabled", "true", 0);
-                Slog.i(TAG, "Migrated lockscreen disabled flag");
-            }
-
-            final List<UserInfo> users = mUserManager.getUsers();
-            for (int i = 0; i < users.size(); i++) {
-                final UserInfo userInfo = users.get(i);
-                if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
-                    // When managed profile has a unified lock, the password quality stored has 2
-                    // possibilities only.
-                    // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
-                    // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
-                    // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
-                    // unified lock.
-                    final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
-                    if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                        // Only possible when it's upgraded from nyc dp3
-                        Slog.i(TAG, "Migrated tied profile lock type");
-                        setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
-                    } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
-                        // It should not happen
-                        Slog.e(TAG, "Invalid tied profile lock type: " + quality);
-                    }
-                }
+                // Migrate owner info enabled. Note there was a bug where older platforms only
+                // stored this value if the checkbox was toggled at least once. The code detects
+                // this case by handling the exception.
+                final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+                boolean enabled;
                 try {
-                    final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
-                    java.security.KeyStore keyStore =
-                            java.security.KeyStore.getInstance("AndroidKeyStore");
-                    keyStore.load(null);
-                    if (keyStore.containsAlias(alias)) {
-                        keyStore.deleteEntry(alias);
+                    int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+                    enabled = ivalue != 0;
+                    setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+                } catch (SettingNotFoundException e) {
+                    // Setting was never stored. Store it if the string is not empty.
+                    if (!TextUtils.isEmpty(ownerInfo)) {
+                        setLong(OWNER_INFO_ENABLED, 1, userId);
                     }
-                } catch (KeyStoreException | NoSuchAlgorithmException |
-                        CertificateException | IOException e) {
-                    Slog.e(TAG, "Unable to remove tied profile key", e);
+                }
+                Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+            }
+            // No need to move the password / pattern files. They're already in the right place.
+            setString("migrated_user_specific", "true", 0);
+            Slog.i(TAG, "Migrated per-user lock settings to new location");
+        }
+
+        // Migrates biometric weak such that the fallback mechanism becomes the primary.
+        if (getString("migrated_biometric_weak", null, 0) == null) {
+            List<UserInfo> users = mUserManager.getUsers();
+            for (int i = 0; i < users.size(); i++) {
+                int userId = users.get(i).id;
+                long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+                        userId);
+                long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+                        userId);
+                if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+                    setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                            alternateType,
+                            userId);
+                }
+                setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+                        userId);
+            }
+            setString("migrated_biometric_weak", "true", 0);
+            Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+        }
+
+        // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
+        // user was present on the system, so if we're upgrading to M and there is more than one
+        // user we disable the flag to remain consistent.
+        if (getString("migrated_lockscreen_disabled", null, 0) == null) {
+            final List<UserInfo> users = mUserManager.getUsers();
+            final int userCount = users.size();
+            int switchableUsers = 0;
+            for (int i = 0; i < userCount; i++) {
+                if (users.get(i).supportsSwitchTo()) {
+                    switchableUsers++;
                 }
             }
 
-            boolean isWatch = mContext.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WATCH);
-            // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
-            // and device management the lockscreen must be re-enabled now for users that upgrade.
-            if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
-                final int userCount = users.size();
+            if (switchableUsers > 1) {
                 for (int i = 0; i < userCount; i++) {
                     int id = users.get(i).id;
-                    setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+
+                    if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
+                        setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+                    }
                 }
-                setString("migrated_wear_lockscreen_disabled", "true", 0);
-                Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
             }
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to migrate old data", re);
+
+            setString("migrated_lockscreen_disabled", "true", 0);
+            Slog.i(TAG, "Migrated lockscreen disabled flag");
+        }
+
+        final List<UserInfo> users = mUserManager.getUsers();
+        for (int i = 0; i < users.size(); i++) {
+            final UserInfo userInfo = users.get(i);
+            if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
+                // When managed profile has a unified lock, the password quality stored has 2
+                // possibilities only.
+                // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
+                // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
+                // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
+                // unified lock.
+                final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+                if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                    // Only possible when it's upgraded from nyc dp3
+                    Slog.i(TAG, "Migrated tied profile lock type");
+                    setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                            DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
+                } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
+                    // It should not happen
+                    Slog.e(TAG, "Invalid tied profile lock type: " + quality);
+                }
+            }
+            try {
+                final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
+                java.security.KeyStore keyStore =
+                        java.security.KeyStore.getInstance("AndroidKeyStore");
+                keyStore.load(null);
+                if (keyStore.containsAlias(alias)) {
+                    keyStore.deleteEntry(alias);
+                }
+            } catch (KeyStoreException | NoSuchAlgorithmException |
+                    CertificateException | IOException e) {
+                Slog.e(TAG, "Unable to remove tied profile key", e);
+            }
+        }
+
+        boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WATCH);
+        // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+        // and device management the lockscreen must be re-enabled now for users that upgrade.
+        if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+            final int userCount = users.size();
+            for (int i = 0; i < userCount; i++) {
+                int id = users.get(i).id;
+                setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+            }
+            setString("migrated_wear_lockscreen_disabled", "true", 0);
+            Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
         }
     }
 
@@ -868,7 +864,7 @@
     }
 
     @Override
-    public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+    public boolean getSeparateProfileChallengeEnabled(int userId) {
         checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
         synchronized (mSeparateChallengeLock) {
             return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
@@ -877,7 +873,7 @@
 
     @Override
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
-            String managedUserPassword) throws RemoteException {
+            String managedUserPassword) {
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
@@ -891,19 +887,19 @@
     }
 
     @Override
-    public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+    public void setBoolean(String key, boolean value, int userId) {
         checkWritePermission(userId);
         setStringUnchecked(key, userId, value ? "1" : "0");
     }
 
     @Override
-    public void setLong(String key, long value, int userId) throws RemoteException {
+    public void setLong(String key, long value, int userId) {
         checkWritePermission(userId);
         setStringUnchecked(key, userId, Long.toString(value));
     }
 
     @Override
-    public void setString(String key, String value, int userId) throws RemoteException {
+    public void setString(String key, String value, int userId) {
         checkWritePermission(userId);
         setStringUnchecked(key, userId, value);
     }
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index be223f1..d71c3b0 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -16,7 +16,7 @@
 
 package com.android.server.media;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.media.AudioPlaybackConfiguration;
 import android.media.IAudioService;
@@ -27,14 +27,15 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -49,47 +50,57 @@
     private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
 
     /**
-     * Called when the state of audio player is changed.
+     * Listener for handling the active state changes of audio players.
      */
-    interface OnAudioPlayerStateChangedListener {
-        void onAudioPlayerStateChanged(
-                int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+    interface OnAudioPlayerActiveStateChangedListener {
+        /**
+         * Called when the active state of audio player is changed.
+         *
+         * @param config The audio playback configuration for the audio player of which active state
+         *              was changed. If {@param isRemoved} is {@code true}, this hold outdated
+         *              information.
+         * @param isRemoved {@code true} if the audio player is removed.
+         */
+        void onAudioPlayerActiveStateChanged(
+                @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
     }
 
     private final static class MessageHandler extends Handler {
-        private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+        private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
 
-        private final OnAudioPlayerStateChangedListener mListsner;
+        private final OnAudioPlayerActiveStateChangedListener mListener;
 
-        public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+        public MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
             super(looper);
-            mListsner = listener;
+            mListener = listener;
         }
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_AUDIO_PLAYER_STATE_CHANGED:
-                    mListsner.onAudioPlayerStateChanged(
-                            msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+                case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
+                    mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
+                            msg.arg1 != 0);
                     break;
             }
         }
 
-        public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
-                AudioPlaybackConfiguration config) {
-            obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+        public void sendAudioPlayerActiveStateChangedMessage(
+                final AudioPlaybackConfiguration config, final boolean isRemoved) {
+            obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
+                    isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
         }
     }
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
-            new HashMap<>();
+    private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
+            new ArrayMap<>();
     @GuardedBy("mLock")
-    private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+    private final Set<Integer> mActiveAudioUids = new ArraySet();
     @GuardedBy("mLock")
-    private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+    private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
+            new ArrayMap<>();
     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
     // The UID whose audio playback becomes active at the last comes first.
     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
@@ -122,32 +133,24 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
-            final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
-                    new HashMap<>(mAudioPlayersForUid);
             synchronized (mLock) {
-                mAudioPlayerStates.clear();
-                mAudioPlayersForUid.clear();
+                // Update mActiveAudioUids
+                mActiveAudioUids.clear();
+                ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
+                        new ArrayMap<>();
                 for (AudioPlaybackConfiguration config : configs) {
-                    int pii = config.getPlayerInterfaceId();
-                    int uid = config.getClientUid();
-                    mAudioPlayerStates.put(pii, config.getPlayerState());
-                    HashSet<Integer> players = mAudioPlayersForUid.get(uid);
-                    if (players == null) {
-                        players = new HashSet<Integer>();
-                        players.add(pii);
-                        mAudioPlayersForUid.put(uid, players);
-                    } else {
-                        players.add(pii);
+                    if (config.isActive()) {
+                        mActiveAudioUids.add(config.getClientUid());
+                        activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
                     }
                 }
-                for (AudioPlaybackConfiguration config : configs) {
-                    if (!config.isActive()) {
-                        continue;
-                    }
 
-                    int uid = config.getClientUid();
-                    if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+                // Update mSortedAuioPlaybackClientUids.
+                for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
+                    AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
+                    final int uid = config.getClientUid();
+                    if (!mPrevActiveAudioPlaybackConfigs.containsKey(
+                            config.getPlayerInterfaceId())) {
                         if (DEBUG) {
                             Log.d(TAG, "Found a new active media playback. " +
                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
@@ -163,40 +166,21 @@
                         mSortedAudioPlaybackClientUids.add(0, uid);
                     }
                 }
-                // Notify the change of audio player states.
+                // Notify the active state change of audio players.
                 for (AudioPlaybackConfiguration config : configs) {
-                    final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
-                    final int prevStateInt =
-                            (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
-                                prevState.intValue();
-                    if (prevStateInt != config.getPlayerState()) {
-                        sendAudioPlayerStateChangedMessageLocked(
-                                config.getClientUid(), prevStateInt, config);
+                    final int pii = config.getPlayerInterfaceId();
+                    boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
+                    if (wasActive != config.isActive()) {
+                        sendAudioPlayerActiveStateChangedMessageLocked(
+                                config, /* isRemoved */ false);
                     }
                 }
-                for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
-                    // If all players for prevUid is removed, notify the prev state was
-                    // PLAYER_STATE_STARTED only when there were a player whose state was
-                    // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
-                    if (!mAudioPlayersForUid.containsKey(prevUid)) {
-                        Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
-                        int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
-                        for (int pii : prevPlayers) {
-                            Integer state = prevAudioPlayerStates.get(pii);
-                            if (state == null) {
-                                continue;
-                            }
-                            if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                                prevState = state;
-                                break;
-                            } else if (prevState
-                                    == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
-                                prevState = state;
-                            }
-                        }
-                        sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
-                    }
+                for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
+                    sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
                 }
+
+                // Update mPrevActiveAudioPlaybackConfigs
+                mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -204,9 +188,10 @@
     }
 
     /**
-     * Registers OnAudioPlayerStateChangedListener.
+     * Registers OnAudioPlayerActiveStateChangedListener.
      */
-    public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+    public void registerListener(
+            OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
         synchronized (mLock) {
             mListenerMap.put(listener, new MessageHandler((handler == null) ?
                     Looper.myLooper() : handler.getLooper(), listener));
@@ -214,9 +199,9 @@
     }
 
     /**
-     * Unregisters OnAudioPlayerStateChangedListener.
+     * Unregisters OnAudioPlayerActiveStateChangedListener.
      */
-    public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+    public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
         synchronized (mLock) {
             mListenerMap.remove(listener);
         }
@@ -239,16 +224,7 @@
      */
     public boolean isPlaybackActive(int uid) {
         synchronized (mLock) {
-            Set<Integer> players = mAudioPlayersForUid.get(uid);
-            if (players == null) {
-                return false;
-            }
-            for (Integer pii : players) {
-                if (isActiveState(mAudioPlayerStates.get(pii))) {
-                    return true;
-                }
-            }
-            return false;
+            return mActiveAudioUids.contains(uid);
         }
     }
 
@@ -314,14 +290,10 @@
         }
     }
 
-    private void sendAudioPlayerStateChangedMessageLocked(
-            final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+    private void sendAudioPlayerActiveStateChangedMessageLocked(
+            final AudioPlaybackConfiguration config, final boolean isRemoved) {
         for (MessageHandler messageHandler : mListenerMap.values()) {
-            messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+            messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
         }
     }
-
-    private static boolean isActiveState(Integer state) {
-        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
-    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3c9e1d4..3e51252 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -19,7 +19,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -101,6 +101,8 @@
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final Handler mHandler = new Handler();
     private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
+    private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
+    private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
 
     public MediaRouterService(Context context) {
         mContext = context;
@@ -111,7 +113,7 @@
 
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
-                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+                new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
             static final long WAIT_MS = 500;
             final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
                 @Override
@@ -121,39 +123,41 @@
             };
 
             @Override
-            public void onAudioPlayerStateChanged(
-                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+            public void onAudioPlayerActiveStateChanged(
+                    @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
+                final boolean active = !isRemoved && config.isActive();
+                final int pii = config.getPlayerInterfaceId();
+                final int uid = config.getClientUid();
+
+                final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
+                // Keep the latest active player and its uid at the end of the queue.
+                if (idx >= 0) {
+                    mActivePlayerMinPriorityQueue.remove(idx);
+                    mActivePlayerUidMinPriorityQueue.remove(idx);
+                }
+
                 int restoreUid = -1;
-                boolean active = config == null ? false : config.isActive();
                 if (active) {
+                    mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
+                    mActivePlayerUidMinPriorityQueue.add(uid);
                     restoreUid = uid;
-                } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                    // Noting to do if the prev state is not an active state.
-                    return;
-                } else {
-                    IntArray sortedAudioPlaybackClientUids =
-                            mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
-                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
-                        if (mAudioPlayerStateMonitor.isPlaybackActive(
-                                sortedAudioPlaybackClientUids.get(i))) {
-                            restoreUid = sortedAudioPlaybackClientUids.get(i);
-                            break;
-                        }
-                    }
+                } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
+                    restoreUid = mActivePlayerUidMinPriorityQueue.get(
+                            mActivePlayerUidMinPriorityQueue.size() - 1);
                 }
 
                 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
                 if (restoreUid >= 0) {
                     restoreRoute(restoreUid);
                     if (DEBUG) {
-                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
-                                + " active " + active + " restoring " + restoreUid);
+                        Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+                                + ", active=" + active + ", restoreUid=" + restoreUid);
                     }
                 } else {
                     mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
                     if (DEBUG) {
-                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
-                                + " active " + active + " delaying");
+                        Slog.d(TAG, "onAudioPlayerACTIVEStateChanged: " + "uid=" + uid
+                                + ", active=" + active + ", delaying");
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index f6a81d0..06f4f5e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.media;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -138,23 +137,19 @@
         mAudioService = getAudioService();
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
-                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
-            @Override
-            public void onAudioPlayerStateChanged(
-                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
-                if (config == null || !config.isActive() || config.getPlayerType()
-                        == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                    return;
-                }
-                synchronized (mLock) {
-                    FullUserRecord user =
-                            getFullUserRecordLocked(UserHandle.getUserId(uid));
-                    if (user != null) {
-                        user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                (config, isRemoved) -> {
+                    if (isRemoved || !config.isActive() || config.getPlayerType()
+                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                        return;
                     }
-                }
-            }
-        }, null /* handler */);
+                    synchronized (mLock) {
+                        FullUserRecord user = getFullUserRecordLocked(
+                                UserHandle.getUserId(config.getClientUid()));
+                        if (user != null) {
+                            user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                        }
+                    }
+                }, null /* handler */);
         mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 557ba42..6ba1d8d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -736,6 +736,11 @@
                 for (NotificationVisibility nv : newlyVisibleKeys) {
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
+                    if (!r.isSeen()) {
+                        // Report to usage stats that notification was made visible
+                        if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
+                        reportSeen(r);
+                    }
                     r.setVisibility(true, nv.rank);
                     nv.recycle();
                 }
@@ -766,7 +771,7 @@
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
                                         : MetricsEvent.TYPE_COLLAPSE));
                     }
-                    if (expanded) {
+                    if (expanded && userAction) {
                         r.recordExpanded();
                     }
                     EventLogTags.writeNotificationExpansion(key,
@@ -1643,6 +1648,14 @@
         return INotificationManager.Stub.asInterface(mService);
     }
 
+    protected void reportSeen(NotificationRecord r) {
+        final int userId = r.sbn.getUserId();
+        mAppUsageStats.reportEvent(r.sbn.getPackageName(),
+                userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
+                        : userId,
+                UsageEvents.Event.NOTIFICATION_SEEN);
+    }
+
     @VisibleForTesting
     NotificationManagerInternal getInternalService() {
         return mInternalService;
@@ -2269,10 +2282,7 @@
                             }
                             if (!r.isSeen()) {
                                 if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]);
-                                mAppUsageStats.reportEvent(r.sbn.getPackageName(),
-                                        userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
-                                                : userId,
-                                        UsageEvents.Event.USER_INTERACTION);
+                                reportSeen(r);
                                 r.setSeen();
                             }
                         }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 679250c..8591304 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 
+import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
@@ -40,6 +41,7 @@
 import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.TimeUnit;
@@ -402,14 +404,22 @@
     }
 
     /**
-     * Execute the idle optimizations immediately.
+     * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
+     * then execute on all packages.
      */
-    public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+    public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
+            @Nullable List<String> packageNames) {
         // Create a new object to make sure we don't interfere with the scheduled jobs.
         // Note that this may still run at the same time with the job scheduled by the
         // JobScheduler but the scheduler will not be able to cancel it.
         BackgroundDexOptService bdos = new BackgroundDexOptService();
-        int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+        ArraySet<String> packagesToOptimize;
+        if (packageNames == null) {
+            packagesToOptimize = pm.getOptimizablePackages();
+        } else {
+            packagesToOptimize = new ArraySet<>(packageNames);
+        }
+        int result = bdos.idleOptimization(pm, packagesToOptimize, context);
         return result == OPTIMIZE_PROCESSED;
     }
 
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 210eb13..6a06be2 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -486,6 +486,16 @@
         }
     }
 
+    public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
+            @Nullable String volumeUuid, int flags) throws InstallerException {
+        if (!checkBeforeRemote()) return new byte[0];
+        try {
+            return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     public void invalidateMounts() throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 83cffe5..bbe59eb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -293,6 +293,7 @@
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
+import com.android.server.pm.dex.DexLogger;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
@@ -2380,7 +2381,10 @@
 
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
-        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
+        DexManager.Listener dexManagerListener = DexLogger.getListener(this,
+                installer, mInstallLock);
+        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
+                dexManagerListener);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -8985,11 +8989,11 @@
      * Execute the background dexopt job immediately.
      */
     @Override
-    public boolean runBackgroundDexoptJob() {
+    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
         }
-        return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
+        return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
     }
 
     List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 807eb1a..44f36d1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -264,7 +264,7 @@
                 PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                         null, null);
                 params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
-                        pkgLite, params.sessionParams.abiOverride));
+                        pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()));
             } catch (PackageParserException | IOException e) {
                 getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
                 throw new IllegalArgumentException(
@@ -1169,11 +1169,17 @@
         }
 
         List<String> failedPackages = new ArrayList<>();
+        int index = 0;
         for (String packageName : packageNames) {
             if (clearProfileData) {
                 mInterface.clearApplicationProfileData(packageName);
             }
 
+            if (allPackages) {
+                pw.println(++index + "/" + packageNames.size() + ": " + packageName);
+                pw.flush();
+            }
+
             boolean result = secondaryDex
                     ? mInterface.performDexOptSecondary(packageName,
                             targetCompilerFilter, forceCompilation)
@@ -1219,7 +1225,13 @@
     }
 
     private int runDexoptJob() throws RemoteException {
-        boolean result = mInterface.runBackgroundDexoptJob();
+        String arg;
+        List<String> packageNames = new ArrayList<>();
+        while ((arg = getNextArg()) != null) {
+            packageNames.add(arg);
+        }
+        boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
+                packageNames);
         return result ? 0 : -1;
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1152310..dbf413f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -717,6 +717,19 @@
         }
     }
 
+    @Override
+    public int getProfileParentId(int userHandle) {
+        checkManageUsersPermission("get the profile parent");
+        synchronized (mUsersLock) {
+            UserInfo profileParent = getProfileParentLU(userHandle);
+            if (profileParent == null) {
+                return userHandle;
+            }
+
+            return profileParent.id;
+        }
+    }
+
     private UserInfo getProfileParentLU(int userHandle) {
         UserInfo profile = getUserInfoLU(userHandle);
         if (profile == null) {
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
new file mode 100644
index 0000000..c7bbf1c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -0,0 +1,116 @@
+/*
+ * 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.pm.dex;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+
+import android.util.ArraySet;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+
+import java.io.File;
+import java.util.Set;
+
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
+/**
+ * This class is responsible for logging data about secondary dex files.
+ * The data logged includes hashes of the name and content of each file.
+ */
+public class DexLogger implements DexManager.Listener {
+    private static final String TAG = "DexLogger";
+
+    // Event log tag & subtag used for SafetyNet logging of dynamic
+    // code loading (DCL) - see b/63927552.
+    private static final int SNET_TAG = 0x534e4554;
+    private static final String DCL_SUBTAG = "dcl";
+
+    private final IPackageManager mPackageManager;
+    private final Object mInstallLock;
+    @GuardedBy("mInstallLock")
+    private final Installer mInstaller;
+
+    public static DexManager.Listener getListener(IPackageManager pms,
+            Installer installer, Object installLock) {
+        return new DexLogger(pms, installer, installLock);
+    }
+
+    private DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+      mPackageManager = pms;
+      mInstaller = installer;
+      mInstallLock = installLock;
+    }
+
+    /**
+     * Compute and log hashes of the name and content of a secondary dex file.
+     */
+    @Override
+    public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+            String dexPath, int storageFlags) {
+        int ownerUid = appInfo.uid;
+
+        byte[] hash = null;
+        synchronized(mInstallLock) {
+            try {
+                hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
+                        ownerUid, appInfo.volumeUuid, storageFlags);
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
+                        " : " + e.getMessage());
+            }
+        }
+        if (hash == null) {
+            return;
+        }
+
+        String dexFileName = new File(dexPath).getName();
+        String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
+        // Valid SHA256 will be 256 bits, 32 bytes.
+        if (hash.length == 32) {
+            message = message + ' ' + ByteStringUtils.toHexString(hash);
+        }
+
+        EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, ownerUid, message);
+
+        if (dexUseInfo.isUsedByOtherApps()) {
+            Set<String> otherPackages = dexUseInfo.getLoadingPackages();
+            Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
+            for (String otherPackageName : otherPackages) {
+                try {
+                    int otherUid = mPackageManager.getPackageUid(
+                        otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
+                    if (otherUid != -1 && otherUid != ownerUid) {
+                        otherUids.add(otherUid);
+                    }
+                } catch (RemoteException ignore) {
+                    // Can't happen, we're local.
+                }
+            }
+            for (int otherUid : otherUids) {
+                EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, otherUid, message);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 6274754..0e2730c 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -76,6 +76,7 @@
     private final Object mInstallLock;
     @GuardedBy("mInstallLock")
     private final Installer mInstaller;
+    private final Listener mListener;
 
     // Possible outcomes of a dex search.
     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
@@ -96,14 +97,24 @@
      */
     private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
 
+    public interface Listener {
+        /**
+         * Invoked just before the secondary dex file {@code dexPath} for the specified application
+         * is reconciled.
+         */
+        void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+                String dexPath, int storageFlags);
+    }
+
     public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
-            Installer installer, Object installLock) {
+            Installer installer, Object installLock, Listener listener) {
       mPackageCodeLocationsCache = new HashMap<>();
       mPackageDexUsage = new PackageDexUsage();
       mPackageManager = pms;
       mPackageDexOptimizer = pdo;
       mInstaller = installer;
       mInstallLock = installLock;
+      mListener = listener;
     }
 
     /**
@@ -389,7 +400,7 @@
                 : mPackageDexOptimizer;
         String packageName = options.getPackageName();
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
             }
@@ -433,7 +444,7 @@
      */
     public void reconcileSecondaryDexFiles(String packageName) {
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
             }
@@ -481,12 +492,16 @@
                 continue;
             }
 
+            if (mListener != null) {
+                mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
+            }
+
             boolean dexStillExists = true;
             synchronized(mInstallLock) {
                 try {
                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
-                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+                            info.uid, isas, info.volumeUuid, flags);
                 } catch (InstallerException e) {
                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
                             " : " + e.getMessage());
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9162a97..7748ae4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -8062,19 +8062,6 @@
     }
 
     @Override
-    public boolean canMagnifyWindow(int windowType) {
-        switch (windowType) {
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
-            case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
-            case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
     public boolean isTopLevelWindow(int windowType) {
         if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
                 && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 2fceae4..336df48 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -32,18 +32,23 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.power.batterysaver.CpuFrequencies;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Class to decide whether to turn on battery saver mode for specific service
  *
- * Test: atest BatterySaverPolicyTest
+ * Test:
+ atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
  */
 public class BatterySaverPolicy extends ContentObserver {
     private static final String TAG = "BatterySaverPolicy";
 
+    public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
+
     // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
     public static final int GPS_MODE_NO_CHANGE = 0;
     // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
@@ -65,8 +70,8 @@
     private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
     private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
 
-    private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:";
-    private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:";
+    private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
+    private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
 
     private static String mSettings;
     private static String mDeviceSpecificSettings;
@@ -173,25 +178,25 @@
     private ContentResolver mContentResolver;
 
     @GuardedBy("mLock")
-    private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+    private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();
 
     /**
      * List of [Filename -> content] that should be written when battery saver is activated
-     * and the screen is on.
+     * and the device is interactive.
      *
      * We use this to change the max CPU frequencies.
      */
     @GuardedBy("mLock")
-    private ArrayMap<String, String> mScreenOnFiles;
+    private ArrayMap<String, String> mFilesForInteractive;
 
     /**
      * List of [Filename -> content] that should be written when battery saver is activated
-     * and the screen is off.
+     * and the device is non-interactive.
      *
      * We use this to change the max CPU frequencies.
      */
     @GuardedBy("mLock")
-    private ArrayMap<String, String> mScreenOffFiles;
+    private ArrayMap<String, String> mFilesForNoninteractive;
 
     public interface BatterySaverPolicyListener {
         void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
@@ -230,11 +235,6 @@
         return R.string.config_batterySaverDeviceSpecificConfig;
     }
 
-    @VisibleForTesting
-    void onChangeForTest() {
-        onChange(true, null);
-    }
-
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         final BatterySaverPolicyListener[] listeners;
@@ -273,6 +273,11 @@
         mSettings = setting;
         mDeviceSpecificSettings = deviceSpecificSetting;
 
+        if (DEBUG) {
+            Slog.i(TAG, "mSettings=" + mSettings);
+            Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
+        }
+
         final KeyValueListParser parser = new KeyValueListParser(',');
 
         // Non-device-specific parameters.
@@ -307,29 +312,11 @@
                     + deviceSpecificSetting);
         }
 
-        mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX);
-        mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX);
-    }
+        mFilesForInteractive = (new CpuFrequencies()).parseString(
+                parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap();
 
-    private static ArrayMap<String, String> collectParams(
-            KeyValueListParser parser, String prefix) {
-        final ArrayMap<String, String> ret = new ArrayMap<>();
-
-        for (int i = parser.size() - 1; i >= 0; i--) {
-            final String key = parser.keyAt(i);
-            if (!key.startsWith(prefix)) {
-                continue;
-            }
-            final String path = key.substring(prefix.length());
-
-            if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) {
-                Slog.wtf(TAG, "Invalid path: " + path);
-                continue;
-            }
-
-            ret.put(path, parser.getString(key, ""));
-        }
-        return ret;
+        mFilesForNoninteractive = (new CpuFrequencies()).parseString(
+                parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
     }
 
     /**
@@ -389,9 +376,9 @@
         }
     }
 
-    public ArrayMap<String, String> getFileValues(boolean screenOn) {
+    public ArrayMap<String, String> getFileValues(boolean interactive) {
         synchronized (mLock) {
-            return screenOn ? mScreenOnFiles : mScreenOffFiles;
+            return interactive ? mFilesForInteractive : mFilesForNoninteractive;
         }
     }
 
@@ -399,10 +386,10 @@
         synchronized (mLock) {
             pw.println();
             pw.println("Battery saver policy");
-            pw.println("  Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
-            pw.println("  value: " + mSettings);
-            pw.println("  Settings " + mDeviceSpecificSettingsSource);
-            pw.println("  value: " + mDeviceSpecificSettings);
+            pw.println("  Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+            pw.println("    value: " + mSettings);
+            pw.println("  Settings: " + mDeviceSpecificSettingsSource);
+            pw.println("    value: " + mDeviceSpecificSettings);
 
             pw.println();
             pw.println("  " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -418,12 +405,12 @@
             pw.println("  " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
             pw.println();
 
-            pw.print("  Screen On Files:\n");
-            dumpMap(pw, "    ", mScreenOnFiles);
+            pw.print("  Interactive File values:\n");
+            dumpMap(pw, "    ", mFilesForInteractive);
             pw.println();
 
-            pw.print("  Screen Off Files:\n");
-            dumpMap(pw, "    ", mScreenOffFiles);
+            pw.print("  Noninteractive File values:\n");
+            dumpMap(pw, "    ", mFilesForNoninteractive);
             pw.println();
         }
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a47b809..0ca0167 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1457,6 +1457,10 @@
                 case PowerManager.GO_TO_SLEEP_REASON_HDMI:
                     Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
                     break;
+                case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
+                    Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
+                            + uid +")...");
+                    break;
                 default:
                     Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
                     reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
@@ -3105,7 +3109,7 @@
         mIsVrModeEnabled = enabled;
     }
 
-    public static void powerHintInternal(int hintId, int data) {
+    private static void powerHintInternal(int hintId, int data) {
         nativeSendPowerHint(hintId, data);
     }
 
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index b3e8538..b471c8d 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -25,6 +25,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.PowerManagerInternal;
 import android.os.PowerManagerInternal.LowPowerModeListener;
 import android.os.PowerSaveState;
 import android.os.UserHandle;
@@ -33,7 +34,9 @@
 import android.widget.Toast;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
 import com.android.server.power.BatterySaverPolicy;
 import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
 import com.android.server.power.PowerManagerService;
@@ -46,7 +49,7 @@
 public class BatterySaverController implements BatterySaverPolicyListener {
     static final String TAG = "BatterySaverController";
 
-    static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+    static final boolean DEBUG = BatterySaverPolicy.DEBUG;
 
     private final Object mLock = new Object();
     private final Context mContext;
@@ -63,20 +66,17 @@
     @GuardedBy("mLock")
     private boolean mEnabled;
 
-    /**
-     * Keep track of the previous enabled state, which we use to decide when to send broadcasts,
-     * which we don't want to send only when the screen state changes.
-     */
-    @GuardedBy("mLock")
-    private boolean mWasEnabled;
-
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_ON:
                 case Intent.ACTION_SCREEN_OFF:
-                    mHandler.postStateChanged();
+                    if (!isEnabled()) {
+                        return; // No need to send it if not enabled.
+                    }
+                    // Don't send the broadcast, because we never did so in this case.
+                    mHandler.postStateChanged(/*sendBroadcast=*/ false);
                     break;
             }
         }
@@ -121,25 +121,32 @@
 
     @Override
     public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
-        mHandler.postStateChanged();
+        if (!isEnabled()) {
+            return; // No need to send it if not enabled.
+        }
+        mHandler.postStateChanged(/*sendBroadcast=*/ true);
     }
 
     private class MyHandler extends Handler {
-        private final int MSG_STATE_CHANGED = 1;
+        private static final int MSG_STATE_CHANGED = 1;
+
+        private static final int ARG_DONT_SEND_BROADCAST = 0;
+        private static final int ARG_SEND_BROADCAST = 1;
 
         public MyHandler(Looper looper) {
             super(looper);
         }
 
-        public void postStateChanged() {
-            obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+        public void postStateChanged(boolean sendBroadcast) {
+            obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
+                    ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, 0).sendToTarget();
         }
 
         @Override
         public void dispatchMessage(Message msg) {
             switch (msg.what) {
                 case MSG_STATE_CHANGED:
-                    handleBatterySaverStateChanged();
+                    handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST);
                     break;
             }
         }
@@ -155,38 +162,53 @@
             }
             mEnabled = enable;
 
-            mHandler.postStateChanged();
+            mHandler.postStateChanged(/*sendBroadcast=*/ true);
+        }
+    }
+
+    /** @return whether battery saver is enabled or not. */
+    boolean isEnabled() {
+        synchronized (mLock) {
+            return mEnabled;
         }
     }
 
     /**
      * Dispatch power save events to the listeners.
      *
-     * This is always called on the handler thread.
+     * This method is always called on the handler thread.
+     *
+     * This method is called only in the following cases:
+     * - When battery saver becomes activated.
+     * - When battery saver becomes deactivated.
+     * - When battery saver is on the interactive state changes.
+     * - When battery saver is on the battery saver policy changes.
      */
-    void handleBatterySaverStateChanged() {
+    void handleBatterySaverStateChanged(boolean sendBroadcast) {
         final LowPowerModeListener[] listeners;
 
-        final boolean wasEnabled;
         final boolean enabled;
-        final boolean isScreenOn = getPowerManager().isInteractive();
+        final boolean isInteractive = getPowerManager().isInteractive();
         final ArrayMap<String, String> fileValues;
 
         synchronized (mLock) {
-            Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+            Slog.i(TAG, "Battery saver " + (mEnabled ? "enabled" : "disabled")
+                    + ": isInteractive=" + isInteractive);
 
             listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
-            wasEnabled = mWasEnabled;
             enabled = mEnabled;
 
             if (enabled) {
-                fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+                fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
             } else {
                 fileValues = null;
             }
         }
 
-        PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0);
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        if (pmi != null) {
+            pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
+        }
 
         if (enabled) {
             // STOPSHIP Remove the toast.
@@ -195,13 +217,13 @@
                     Toast.LENGTH_LONG).show();
         }
 
-        if (fileValues == null || fileValues.size() == 0) {
+        if (ArrayUtils.isEmpty(fileValues)) {
             mFileUpdater.restoreDefault();
         } else {
             mFileUpdater.writeFiles(fileValues);
         }
 
-        if (enabled != wasEnabled) {
+        if (sendBroadcast) {
             if (DEBUG) {
                 Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
             }
@@ -231,9 +253,5 @@
                 listener.onLowPowerModeChanged(result);
             }
         }
-
-        synchronized (mLock) {
-            mWasEnabled = enabled;
-        }
     }
 }
diff --git a/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 0000000..1629486
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
@@ -0,0 +1,101 @@
+/*
+ * 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.power.batterysaver;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+
+
+/**
+ * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator,
+ * and convert them into a map of "filename -> value" that should be written to
+ * /sys/.../scaling_max_freq.
+ *
+ * Example input: "0:1900800/4:2500000", which will be converted into:
+ *   "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800"
+ *   "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000"
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+public class CpuFrequencies {
+    private static final String TAG = "CpuFrequencies";
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>();
+
+    public CpuFrequencies() {
+    }
+
+    /**
+     * Parse a string.
+     */
+    public CpuFrequencies parseString(String cpuNumberAndFrequencies) {
+        synchronized (mLock) {
+            mCoreAndFrequencies.clear();
+            try {
+                for (String pair : cpuNumberAndFrequencies.split("/")) {
+                    final String[] coreAndFreq = pair.split(":", 2);
+
+                    if (coreAndFreq.length != 2) {
+                        throw new IllegalArgumentException("Wrong format");
+                    }
+                    final int core = Integer.parseInt(coreAndFreq[0]);
+                    final long freq = Long.parseLong(coreAndFreq[1]);
+
+                    mCoreAndFrequencies.put(core, freq);
+                }
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Return a new map containing the filename-value pairs.
+     */
+    public ArrayMap<String, String> toSysFileMap() {
+        final ArrayMap<String, String> map = new ArrayMap<>();
+        addToSysFileMap(map);
+        return map;
+    }
+
+    /**
+     * Add the filename-value pairs to an existing map.
+     */
+    public void addToSysFileMap(Map<String, String> map) {
+        synchronized (mLock) {
+            final int size = mCoreAndFrequencies.size();
+
+            for (int i = 0; i < size; i++) {
+                final int core = mCoreAndFrequencies.keyAt(i);
+                final long freq = mCoreAndFrequencies.valueAt(i);
+
+                final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) +
+                        "/cpufreq/scaling_max_freq";
+
+                map.put(file, Long.toString(freq));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc4..cc1b540 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,259 @@
 package com.android.server.power.batterysaver;
 
 import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
 /**
  * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
- * with retry and to restore the original values.
+ * with retries. It also support restoring to the file original values.
  *
- * TODO Implement it
+ * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current
+ * frequency happens to be above the new max frequency.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
  */
 public class FileUpdater {
     private static final String TAG = BatterySaverController.TAG;
 
     private static final boolean DEBUG = BatterySaverController.DEBUG;
 
+    // Don't do disk access with this lock held.
     private final Object mLock = new Object();
+
     private final Context mContext;
 
+    private final Handler mHandler;
+
+    /**
+     * Filename -> value map that holds pending writes.
+     */
+    @GuardedBy("mLock")
+    private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>();
+
+    /**
+     * Filename -> value that holds the original value of each file.
+     */
+    @GuardedBy("mLock")
+    private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>();
+
+    /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */
+    @GuardedBy("mLock")
+    private int mRetries = 0;
+
+    private final int MAX_RETRIES;
+
+    private final long RETRY_INTERVAL_MS;
+
+    /**
+     * "Official" constructor. Don't use the other constructor in the production code.
+     */
     public FileUpdater(Context context) {
-        mContext = context;
+        this(context, IoThread.get().getLooper(), 10, 5000);
     }
 
+    /**
+     * Constructor for test.
+     */
+    @VisibleForTesting
+    FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+        mContext = context;
+        mHandler = new Handler(looper);
+
+        MAX_RETRIES = maxRetries;
+        RETRY_INTERVAL_MS = retryIntervalMs;
+    }
+
+    /**
+     * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+     */
     public void writeFiles(ArrayMap<String, String> fileValues) {
-        if (DEBUG) {
-            final int size = fileValues.size();
-            for (int i = 0; i < size; i++) {
-                Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
-                        + "' to '" + fileValues.keyAt(i) + "'");
+        synchronized (mLock) {
+            for (int i = fileValues.size() - 1; i >= 0; i--) {
+                final String file = fileValues.keyAt(i);
+                final String value = fileValues.valueAt(i);
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'");
+                }
+
+                mPendingWrites.put(file, value);
+
+            }
+            mRetries = 0;
+
+            mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+            mHandler.post(mHandleWriteOnHandlerRunnable);
+        }
+    }
+
+    /**
+     * Restore the default values.
+     */
+    public void restoreDefault() {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Resetting file default values.");
+            }
+            mPendingWrites.clear();
+
+            writeFiles(mDefaultValues);
+        }
+    }
+
+    private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler();
+
+    /** Convert map keys into a single string for debug messages. */
+    private String getKeysString(Map<String, String> source) {
+        return new ArrayList<>(source.keySet()).toString();
+    }
+
+    /** Clone an ArrayMap. */
+    private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) {
+        return new ArrayMap<>(source);
+    }
+
+    /**
+     * Called on the handler and writes {@link #mPendingWrites} to the disk.
+     *
+     * When it about to write to each file for the first time, it'll read the file and store
+     * the original value in {@link #mDefaultValues}.
+     */
+    private void handleWriteOnHandler() {
+        // We don't want to access the disk with the lock held, so copy the pending writes to
+        // a local map.
+        final ArrayMap<String, String> writes;
+        synchronized (mLock) {
+            if (mPendingWrites.size() == 0) {
+                return;
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " +
+                        getKeysString(mPendingWrites));
+            }
+
+            writes = cloneMap(mPendingWrites);
+        }
+
+        // Then write.
+
+        boolean needRetry = false;
+
+        final int size = writes.size();
+        for (int i = 0; i < size; i++) {
+            final String file = writes.keyAt(i);
+            final String value = writes.valueAt(i);
+
+            // Make sure the default value is loaded.
+            if (!ensureDefaultLoaded(file)) {
+                continue;
+            }
+
+            // Write to the file. When succeeded, remove it from the pending list.
+            // Otherwise, schedule a retry.
+            try {
+                injectWriteToFile(file, value);
+
+                removePendingWrite(file);
+            } catch (IOException e) {
+                needRetry = true;
             }
         }
+        if (needRetry) {
+            scheduleRetry();
+        }
     }
 
-    public void restoreDefault() {
-        if (DEBUG) {
-            Slog.d(TAG, "Resetting file default values");
+    private void removePendingWrite(String file) {
+        synchronized (mLock) {
+            mPendingWrites.remove(file);
         }
     }
+
+    private void scheduleRetry() {
+        synchronized (mLock) {
+            if (mPendingWrites.size() == 0) {
+                return; // Shouldn't happen but just in case.
+            }
+
+            mRetries++;
+            if (mRetries > MAX_RETRIES) {
+                doWtf("Gave up writing files: " + getKeysString(mPendingWrites));
+                return;
+            }
+
+            mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+            mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS);
+        }
+    }
+
+    /**
+     * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}.
+     *
+     * @return true if the default value is loaded. false if the file cannot be read.
+     */
+    private boolean ensureDefaultLoaded(String file) {
+        // Has the default already?
+        synchronized (mLock) {
+            if (mDefaultValues.containsKey(file)) {
+                return true;
+            }
+        }
+        final String originalValue;
+        try {
+            originalValue = injectReadFromFileTrimmed(file);
+        } catch (IOException e) {
+            // If the file is not readable, assume can't write too.
+            injectWtf("Unable to read from file", e);
+
+            removePendingWrite(file);
+            return false;
+        }
+        synchronized (mLock) {
+            mDefaultValues.put(file, originalValue);
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    String injectReadFromFileTrimmed(String file) throws IOException {
+        return IoUtils.readFileAsString(file).trim();
+    }
+
+    @VisibleForTesting
+    void injectWriteToFile(String file, String value) throws IOException {
+        if (DEBUG) {
+            Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+        }
+        try (FileWriter out = new FileWriter(file)) {
+            out.write(value);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+            throw e;
+        }
+    }
+
+    private void doWtf(String message) {
+        injectWtf(message, null);
+    }
+
+    @VisibleForTesting
+    void injectWtf(String message, Throwable e) {
+        Slog.wtf(TAG, message, e);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index de4fd7cd..88d1e55 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -292,6 +292,8 @@
         public void setMagnificationSpecLocked(MagnificationSpec spec) {
             mMagnifedViewport.updateMagnificationSpecLocked(spec);
             mMagnifedViewport.recomputeBoundsLocked();
+
+            mService.applyMagnificationSpec(spec);
             mService.scheduleAnimationLocked();
         }
 
@@ -421,7 +423,7 @@
         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
             if (spec != null && !spec.isNop()) {
-                if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                if (!windowState.shouldMagnify()) {
                     return null;
                 }
             }
@@ -476,6 +478,7 @@
             private final ViewportWindow mWindow;
 
             private boolean mFullRedrawNeeded;
+            private int mTempLayer = 0;
 
             public MagnifiedViewport() {
                 mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
@@ -565,7 +568,7 @@
                     portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
                     windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
 
-                    if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                    if (windowState.shouldMagnify()) {
                         mMagnificationRegion.op(windowBounds, Region.Op.UNION);
                         mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
                     } else {
@@ -676,10 +679,12 @@
 
             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
                 final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+                mTempLayer = 0;
                 dc.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisibleLw()
                             && !w.mWinAnimator.mEnterAnimationPending) {
-                        outWindows.put(w.mLayer, w);
+                        mTempLayer++;
+                        outWindows.put(mTempLayer, w);
                     }
                 }, false /* traverseTopToBottom */ );
             }
@@ -705,7 +710,7 @@
                     SurfaceControl surfaceControl = null;
                     try {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
-                        surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                        surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
                                 .setName(SURFACE_TITLE)
                                 .setSize(mTempPoint.x, mTempPoint.y) // not a typo
                                 .setFormat(PixelFormat.TRANSLUCENT)
@@ -714,8 +719,6 @@
                         /* ignore */
                     }
                     mSurfaceControl = surfaceControl;
-                    mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
-                            .getLayerStack());
                     mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
                             TYPE_MAGNIFICATION_OVERLAY)
                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
@@ -1005,6 +1008,8 @@
 
         private final long mRecurringAccessibilityEventsIntervalMillis;
 
+        private int mTempLayer = 0;
+
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 WindowsForAccessibilityCallback callback) {
             mContext = windowManagerService.mContext;
@@ -1090,6 +1095,7 @@
                     if (isReportedWindowType(windowState.mAttrs.type)) {
                         // Add the window to the ones to be reported.
                         WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
+                        window.layer = addedWindows.size();
                         addedWindows.add(window.token);
                         windows.add(window);
                         if (windowState.isFocused()) {
@@ -1323,9 +1329,10 @@
 
         private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
             final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+            mTempLayer = 0;
             dc.forAllWindows((w) -> {
                 if (w.isVisibleLw()) {
-                    outWindows.put(w.mLayer, w);
+                    outWindows.put(mTempLayer++, w);
                 }
             }, false /* traverseTopToBottom */ );
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2ef7f25..accfc65 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -253,7 +253,6 @@
 
     private void updateLayers() {
         mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-        updateThumbnailLayer();
     }
 
     private void stepThumbnailAnimation(long currentTime) {
@@ -283,27 +282,12 @@
                 + "][" + tmpFloats[Matrix.MSKEW_X]
                 + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
         thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        updateThumbnailLayer();
         thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
         thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
     }
 
     /**
-     * Updates the thumbnail layer z order to just above the highest animation layer if changed
-     */
-    void updateThumbnailLayer() {
-        if (thumbnail != null) {
-            final int layer = mAppToken.getHighestAnimLayer();
-            if (DEBUG_LAYERS) Slog.v(TAG,
-                    "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
-            thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-            mThumbnailLayer = layer;
-        }
-    }
-
-    /**
      * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
      * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
      * and keep producing the first frame of the animation.
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 9729e50..f19cd0f 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -30,7 +30,6 @@
 import android.util.Slog;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 
 /**
  * Four black surfaces put together to make a black frame.
@@ -42,22 +41,22 @@
         final int layer;
         final SurfaceControl surface;
 
-        BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
-                throws OutOfResourcesException {
+        BlackSurface(int layer,
+                int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException {
             left = l;
             top = t;
             this.layer = layer;
             int w = r-l;
             int h = b-t;
 
-            surface = new SurfaceControl.Builder(session)
+            surface = dc.makeOverlay()
                     .setName("BlackSurface")
                     .setSize(w, h)
                     .setColorLayer(true)
+                    .setParent(null) // TODO: Work-around for b/69259549
                     .build();
 
             surface.setAlpha(1);
-            surface.setLayerStack(layerStack);
             surface.setLayer(layer);
             surface.show();
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
@@ -114,30 +113,32 @@
         }
     }
 
-    public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+    public BlackFrame(Rect outer, Rect inner, int layer, DisplayContent dc,
             boolean forceDefaultOrientation) throws OutOfResourcesException {
         boolean success = false;
 
         mForceDefaultOrientation = forceDefaultOrientation;
 
+        // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
+        // b/68253229
         mOuterRect = new Rect(outer);
         mInnerRect = new Rect(inner);
         try {
             if (outer.top < inner.top) {
-                mBlackSurfaces[0] = new BlackSurface(session, layer,
-                        outer.left, outer.top, inner.right, inner.top, layerStack);
+                mBlackSurfaces[0] = new BlackSurface(layer,
+                        outer.left, outer.top, inner.right, inner.top, dc);
             }
             if (outer.left < inner.left) {
-                mBlackSurfaces[1] = new BlackSurface(session, layer,
-                        outer.left, inner.top, inner.left, outer.bottom, layerStack);
+                mBlackSurfaces[1] = new BlackSurface(layer,
+                        outer.left, inner.top, inner.left, outer.bottom, dc);
             }
             if (outer.bottom > inner.bottom) {
-                mBlackSurfaces[2] = new BlackSurface(session, layer,
-                        inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+                mBlackSurfaces[2] = new BlackSurface(layer,
+                        inner.left, inner.bottom, outer.right, outer.bottom, dc);
             }
             if (outer.right > inner.right) {
-                mBlackSurfaces[3] = new BlackSurface(session, layer,
-                        inner.right, outer.top, outer.right, inner.bottom, layerStack);
+                mBlackSurfaces[3] = new BlackSurface(layer,
+                        inner.right, outer.top, outer.right, inner.bottom, dc);
             }
             success = true;
         } finally {
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 2d5d1b2..2a216ab 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -33,7 +33,6 @@
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 
 class CircularDisplayMask {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
@@ -54,8 +53,10 @@
     private boolean mDimensionsUnequal = false;
     private int mMaskThickness;
 
-    public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
+    public CircularDisplayMask(DisplayContent dc, int zOrder,
             int screenOffset, int maskThickness) {
+        final Display display = dc.getDisplay();
+
         mScreenSize = new Point();
         display.getSize(mScreenSize);
         if (mScreenSize.x != mScreenSize.y + screenOffset) {
@@ -66,7 +67,7 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("CircularDisplayMask")
                     .setSize(mScreenSize.x, mScreenSize.y) // not a typo
                     .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index cc94807..b534b8a 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -375,6 +375,10 @@
         return toString();
     }
 
+    boolean isAlwaysOnTop() {
+        return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
deleted file mode 100644
index 8fb2be8..0000000
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ /dev/null
@@ -1,380 +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.server.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-
-import java.io.PrintWriter;
-
-public class DimLayer {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM;
-    private final WindowManagerService mService;
-
-    /** Actual surface that dims */
-    private SurfaceControl mDimSurface;
-
-    /** Last value passed to mDimSurface.setAlpha() */
-    private float mAlpha = 0;
-
-    /** Last value passed to mDimSurface.setLayer() */
-    private int mLayer = -1;
-
-    /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
-    private final Rect mBounds = new Rect();
-
-    /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
-    private final Rect mLastBounds = new Rect();
-
-    /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
-    private boolean mShowing = false;
-
-    /** Value of mAlpha when beginning transition to mTargetAlpha */
-    private float mStartAlpha = 0;
-
-    /** Final value of mAlpha following transition */
-    private float mTargetAlpha = 0;
-
-    /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
-    private long mStartTime;
-
-    /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
-    private long mDuration;
-
-    private boolean mDestroyed = false;
-
-    private final int mDisplayId;
-
-
-    /** Interface implemented by users of the dim layer */
-    interface DimLayerUser {
-        /** Returns true if the  dim should be fullscreen. */
-        boolean dimFullscreen();
-        /** Returns the display info. of the dim layer user. */
-        DisplayInfo getDisplayInfo();
-        /** Returns true if the dim layer user is currently attached to a display */
-        boolean isAttachedToDisplay();
-        /** Gets the bounds of the dim layer user. */
-        void getDimBounds(Rect outBounds);
-        /** Returns the layer to place a dim layer. */
-        default int getLayerForDim(WindowStateAnimator animator, int layerOffset,
-                int defaultLayer) {
-            return defaultLayer;
-        }
-
-        String toShortString();
-    }
-    /** The user of this dim layer. */
-    private final DimLayerUser mUser;
-
-    private final String mName;
-
-    DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) {
-        mUser = user;
-        mDisplayId = displayId;
-        mService = service;
-        mName = name;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId);
-    }
-
-    private void constructSurface(WindowManagerService service) {
-        service.openSurfaceTransaction();
-        try {
-            mDimSurface = new SurfaceControl.Builder(service.mFxSession)
-                    .setName(mName)
-                    .setSize(16, 16)
-                    .setColorLayer(true)
-                    .build();
-
-            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
-                    "  DIM " + mDimSurface + ": CREATE");
-            mDimSurface.setLayerStack(mDisplayId);
-            adjustBounds();
-            adjustAlpha(mAlpha);
-            adjustLayer(mLayer);
-        } catch (Exception e) {
-            Slog.e(TAG_WM, "Exception creating Dim surface", e);
-        } finally {
-            service.closeSurfaceTransaction("DimLayer.constructSurface");
-        }
-    }
-
-    /** Return true if dim layer is showing */
-    boolean isDimming() {
-        return mTargetAlpha != 0;
-    }
-
-    /** Return true if in a transition period */
-    boolean isAnimating() {
-        return mTargetAlpha != mAlpha;
-    }
-
-    float getTargetAlpha() {
-        return mTargetAlpha;
-    }
-
-    void setLayer(int layer) {
-        if (mLayer == layer) {
-            return;
-        }
-        mLayer = layer;
-        adjustLayer(layer);
-    }
-
-    private void adjustLayer(int layer) {
-        if (mDimSurface != null) {
-            mDimSurface.setLayer(layer);
-        }
-    }
-
-    int getLayer() {
-        return mLayer;
-    }
-
-    private void setAlpha(float alpha) {
-        if (mAlpha == alpha) {
-            return;
-        }
-        mAlpha = alpha;
-        adjustAlpha(alpha);
-    }
-
-    private void adjustAlpha(float alpha) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha);
-        try {
-            if (mDimSurface != null) {
-                mDimSurface.setAlpha(alpha);
-            }
-            if (alpha == 0 && mShowing) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding");
-                if (mDimSurface != null) {
-                    mDimSurface.hide();
-                    mShowing = false;
-                }
-            } else if (alpha > 0 && !mShowing) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing");
-                if (mDimSurface != null) {
-                    mDimSurface.show();
-                    mShowing = true;
-                }
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Failure setting alpha immediately", e);
-        }
-    }
-
-    /**
-     * NOTE: Must be called with Surface transaction open.
-     */
-    private void adjustBounds() {
-        if (mUser.dimFullscreen()) {
-            getBoundsForFullscreen(mBounds);
-        }
-
-        if (mDimSurface != null) {
-            mDimSurface.setPosition(mBounds.left, mBounds.top);
-            mDimSurface.setSize(mBounds.width(), mBounds.height());
-            if (DEBUG_DIM_LAYER) Slog.v(TAG,
-                    "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds);
-        }
-
-        mLastBounds.set(mBounds);
-    }
-
-    private void getBoundsForFullscreen(Rect outBounds) {
-        final int dw, dh;
-        final float xPos, yPos;
-        // Set surface size to screen size.
-        final DisplayInfo info = mUser.getDisplayInfo();
-        // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
-        // a corner.
-        dw = (int) (info.logicalWidth * 1.5);
-        dh = (int) (info.logicalHeight * 1.5);
-        // back off position so 1/4 of Surface is before and 1/4 is after.
-        xPos = -1 * dw / 6;
-        yPos = -1 * dh / 6;
-        outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh);
-    }
-
-    void setBoundsForFullscreen() {
-        getBoundsForFullscreen(mBounds);
-        setBounds(mBounds);
-    }
-
-    /** @param bounds The new bounds to set */
-    void setBounds(Rect bounds) {
-        mBounds.set(bounds);
-        if (isDimming() && !mLastBounds.equals(bounds)) {
-            try {
-                mService.openSurfaceTransaction();
-                adjustBounds();
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Failure setting size", e);
-            } finally {
-                mService.closeSurfaceTransaction("DimLayer.setBounds");
-            }
-        }
-    }
-
-    /**
-     * @param duration The time to test.
-     * @return True if the duration would lead to an earlier end to the current animation.
-     */
-    private boolean durationEndsEarlier(long duration) {
-        return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
-    }
-
-    /** Jump to the end of the animation.
-     * NOTE: Must be called with Surface transaction open. */
-    void show() {
-        if (isAnimating()) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate");
-            show(mLayer, mTargetAlpha, 0);
-        }
-    }
-
-    /**
-     * Begin an animation to a new dim value.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @param layer The layer to set the surface to.
-     * @param alpha The dim value to end at.
-     * @param duration How long to take to get there in milliseconds.
-     */
-    void show(int layer, float alpha, long duration) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
-                + " duration=" + duration + ", mDestroyed=" + mDestroyed);
-        if (mDestroyed) {
-            Slog.e(TAG, "show: no Surface");
-            // Make sure isAnimating() returns false.
-            mTargetAlpha = mAlpha = 0;
-            return;
-        }
-
-        if (mDimSurface == null) {
-            constructSurface(mService);
-        }
-
-        if (!mLastBounds.equals(mBounds)) {
-            adjustBounds();
-        }
-        setLayer(layer);
-
-        long curTime = SystemClock.uptimeMillis();
-        final boolean animating = isAnimating();
-        if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
-                || (!animating && mAlpha != alpha)) {
-            if (duration <= 0) {
-                // No animation required, just set values.
-                setAlpha(alpha);
-            } else {
-                // Start or continue animation with new parameters.
-                mStartAlpha = mAlpha;
-                mStartTime = curTime;
-                mDuration = duration;
-            }
-        }
-        mTargetAlpha = alpha;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime="
-                + mStartTime + " mTargetAlpha=" + mTargetAlpha);
-    }
-
-    /** Immediate hide.
-     * NOTE: Must be called with Surface transaction open. */
-    void hide() {
-        if (mShowing) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate");
-            hide(0);
-        }
-    }
-
-    /**
-     * Gradually fade to transparent.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @param duration Time to fade in milliseconds.
-     */
-    void hide(long duration) {
-        if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration);
-            show(mLayer, 0, duration);
-        }
-    }
-
-    /**
-     * Advance the dimming per the last #show(int, float, long) call.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @return True if animation is still required after this step.
-     */
-    boolean stepAnimation() {
-        if (mDestroyed) {
-            Slog.e(TAG, "stepAnimation: surface destroyed");
-            // Ensure that isAnimating() returns false;
-            mTargetAlpha = mAlpha = 0;
-            return false;
-        }
-        if (isAnimating()) {
-            final long curTime = SystemClock.uptimeMillis();
-            final float alphaDelta = mTargetAlpha - mStartAlpha;
-            float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
-            if (alphaDelta > 0 && alpha > mTargetAlpha ||
-                    alphaDelta < 0 && alpha < mTargetAlpha) {
-                // Don't exceed limits.
-                alpha = mTargetAlpha;
-            }
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
-            setAlpha(alpha);
-        }
-
-        return isAnimating();
-    }
-
-    /** Cleanup */
-    void destroySurface() {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface.");
-        if (mDimSurface != null) {
-            mDimSurface.destroy();
-            mDimSurface = null;
-        }
-        mDestroyed = true;
-    }
-
-    public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
-                pw.print(" mLayer="); pw.print(mLayer);
-                pw.print(" mAlpha="); pw.println(mAlpha);
-        pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
-                pw.print(" mBounds="); pw.println(mBounds.toShortString());
-        pw.print(prefix); pw.print("Last animation: ");
-                pw.print(" mDuration="); pw.print(mDuration);
-                pw.print(" mStartTime="); pw.print(mStartTime);
-                pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
-        pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
-                pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
deleted file mode 100644
index 6f9e45a..0000000
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package com.android.server.wm;
-
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
-
-import android.graphics.Rect;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.TypedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.DimLayer.DimLayerUser;
-
-import java.io.PrintWriter;
-
-/**
- * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
- * as well as other use cases (such as dimming above a dead window).
- */
-class DimLayerController {
-    private static final String TAG_LOCAL = "DimLayerController";
-    private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
-    /** Amount of time in milliseconds to animate the dim surface from one value to another,
-     * when no window animation is driving it. */
-    private static final int DEFAULT_DIM_DURATION = 200;
-
-    /**
-     * The default amount of dim applied over a dead window
-     */
-    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
-
-    // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
-    // instead of creating a new object per fullscreen task on a display.
-    private DimLayer mSharedFullScreenDimLayer;
-
-    private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
-
-    private DisplayContent mDisplayContent;
-
-    private Rect mTmpBounds = new Rect();
-
-    DimLayerController(DisplayContent displayContent) {
-        mDisplayContent = displayContent;
-    }
-
-    /** Updates the dim layer bounds, recreating it if needed. */
-    void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
-        final boolean previousFullscreen = state.dimLayer != null
-                && state.dimLayer == mSharedFullScreenDimLayer;
-        DimLayer newDimLayer;
-        final int displayId = mDisplayContent.getDisplayId();
-        if (dimLayerUser.dimFullscreen()) {
-            if (previousFullscreen && mSharedFullScreenDimLayer != null) {
-                // Update the bounds for fullscreen in case of rotation.
-                mSharedFullScreenDimLayer.setBoundsForFullscreen();
-                return;
-            }
-            // Use shared fullscreen dim layer
-            newDimLayer = mSharedFullScreenDimLayer;
-            if (newDimLayer == null) {
-                if (state.dimLayer != null) {
-                    // Re-purpose the previous dim layer.
-                    newDimLayer = state.dimLayer;
-                } else {
-                    // Create new full screen dim layer.
-                    newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
-                            getDimLayerTag(dimLayerUser));
-                }
-                dimLayerUser.getDimBounds(mTmpBounds);
-                newDimLayer.setBounds(mTmpBounds);
-                mSharedFullScreenDimLayer = newDimLayer;
-            } else if (state.dimLayer != null) {
-                state.dimLayer.destroySurface();
-            }
-        } else {
-            newDimLayer = (state.dimLayer == null || previousFullscreen)
-                    ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
-                            getDimLayerTag(dimLayerUser))
-                    : state.dimLayer;
-            dimLayerUser.getDimBounds(mTmpBounds);
-            newDimLayer.setBounds(mTmpBounds);
-        }
-        state.dimLayer = newDimLayer;
-    }
-
-    private static String getDimLayerTag(DimLayerUser dimLayerUser) {
-        return TAG_LOCAL + "/" + dimLayerUser.toShortString();
-    }
-
-    private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
-                + dimLayerUser.toShortString());
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state == null) {
-            state = new DimLayerState();
-            mState.put(dimLayerUser, state);
-        }
-        return state;
-    }
-
-    private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state == null) {
-            if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
-                    + dimLayerUser.toShortString());
-            return;
-        }
-        state.continueDimming = true;
-    }
-
-    boolean isDimming() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayerState state = mState.valueAt(i);
-            if (state.dimLayer != null && state.dimLayer.isDimming()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void resetDimming() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            mState.valueAt(i).continueDimming = false;
-        }
-    }
-
-    private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        return state != null && state.continueDimming;
-    }
-
-    void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
-            WindowStateAnimator newWinAnimator, boolean aboveApp) {
-        // Only set dim params on the highest dimmed layer.
-        // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
-        state.dimAbove = aboveApp;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " newWinAnimator=" + newWinAnimator
-                + " state.animator=" + state.animator);
-        if (newWinAnimator.getShown() && (state.animator == null
-                || !state.animator.getShown()
-                || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
-            state.animator = newWinAnimator;
-            if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
-                // Dim should cover the entire screen for system windows.
-                mDisplayContent.getLogicalDisplayRect(mTmpBounds);
-            } else {
-                dimLayerUser.getDimBounds(mTmpBounds);
-            }
-            state.dimLayer.setBounds(mTmpBounds);
-        }
-    }
-
-    void stopDimmingIfNeeded() {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
-            stopDimmingIfNeeded(dimLayerUser);
-        }
-    }
-
-    private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
-        // No need to check if state is null, we know the key has a value.
-        DimLayerState state = mState.get(dimLayerUser);
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " state.continueDimming=" + state.continueDimming
-                + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
-        if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
-            return;
-        }
-
-        if (!state.continueDimming && state.dimLayer.isDimming()) {
-            state.animator = null;
-            dimLayerUser.getDimBounds(mTmpBounds);
-            state.dimLayer.setBounds(mTmpBounds);
-        }
-    }
-
-    boolean animateDimLayers() {
-        int fullScreen = -1;
-        int fullScreenAndDimming = -1;
-        int topFullScreenUserLayer = 0;
-        boolean result = false;
-
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            final DimLayer.DimLayerUser user = mState.keyAt(i);
-            final DimLayerState state = mState.valueAt(i);
-
-            if (!user.isAttachedToDisplay()) {
-                // Leaked dim user that is no longer attached to the display. Go ahead and clean it
-                // clean-up and log what happened.
-                // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
-                // it self when it was detached from the display. Need to investigate how the dim
-                // user is leaking...
-                //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
-                //        + " state=" + state);
-                Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
-                removeDimLayerUser(user);
-                continue;
-            }
-
-            // We have to check that we are actually the shared fullscreen layer
-            // for this path. If we began as non fullscreen and became fullscreen
-            // (e.g. Docked stack closing), then we may not be the shared layer
-            // and we have to make sure we always animate the layer.
-            if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
-                fullScreen = i;
-                if (!state.continueDimming) {
-                    continue;
-                }
-
-                // When choosing which user to assign the shared fullscreen layer to
-                // we need to look at Z-order.
-                if (topFullScreenUserLayer == 0 ||
-                        (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
-                    fullScreenAndDimming = i;
-                    if (state.animator != null) {
-                        topFullScreenUserLayer = state.animator.mAnimLayer;
-                    }
-                }
-            } else {
-                // We always want to animate the non fullscreen windows, they don't share their
-                // dim layers.
-                result |= animateDimLayers(user);
-            }
-        }
-        // For the shared, full screen dim layer, we prefer the animation that is causing it to
-        // appear.
-        if (fullScreenAndDimming != -1) {
-            result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
-        } else if (fullScreen != -1) {
-            // If there is no animation for the full screen dim layer to appear, we can use any of
-            // the animators that will cause it to disappear.
-            result |= animateDimLayers(mState.keyAt(fullScreen));
-        }
-        return result;
-    }
-
-    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " state.animator=" + state.animator
-                + " state.continueDimming=" + state.continueDimming);
-        final int dimLayer;
-        final float dimAmount;
-        if (state.animator == null) {
-            dimLayer = state.dimLayer.getLayer();
-            dimAmount = 0;
-        } else {
-            if (state.dimAbove) {
-                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
-                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
-            } else {
-                dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
-                        state.animator.mAnimLayer - LAYER_OFFSET_DIM);
-                dimAmount = state.animator.mWin.mAttrs.dimAmount;
-            }
-        }
-        final float targetAlpha = state.dimLayer.getTargetAlpha();
-        if (targetAlpha != dimAmount) {
-            if (state.animator == null) {
-                state.dimLayer.hide(DEFAULT_DIM_DURATION);
-            } else {
-                long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
-                        ? state.animator.mAnimation.computeDurationHint()
-                        : DEFAULT_DIM_DURATION;
-                if (targetAlpha > dimAmount) {
-                    duration = getDimLayerFadeDuration(duration);
-                }
-                state.dimLayer.show(dimLayer, dimAmount, duration);
-
-                // If we showed a dim layer, make sure to redo the layout because some things depend
-                // on whether a dim layer is showing or not.
-                if (targetAlpha == 0) {
-                    mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
-                    mDisplayContent.setLayoutNeeded();
-                }
-            }
-        } else if (state.dimLayer.getLayer() != dimLayer) {
-            state.dimLayer.setLayer(dimLayer);
-        }
-        if (state.dimLayer.isAnimating()) {
-            if (!mDisplayContent.okToAnimate()) {
-                // Jump to the end of the animation.
-                state.dimLayer.show();
-            } else {
-                return state.dimLayer.stepAnimation();
-            }
-        }
-        return false;
-    }
-
-    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
-        DimLayerState state = mState.get(dimLayerUser);
-        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
-    }
-
-    private long getDimLayerFadeDuration(long duration) {
-        TypedValue tv = new TypedValue();
-        mDisplayContent.mService.mContext.getResources().getValue(
-                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
-        if (tv.type == TypedValue.TYPE_FRACTION) {
-            duration = (long) tv.getFraction(duration, duration);
-        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
-            duration = tv.data;
-        }
-        return duration;
-    }
-
-    void close() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayerState state = mState.valueAt(i);
-            state.dimLayer.destroySurface();
-        }
-        mState.clear();
-        mSharedFullScreenDimLayer = null;
-    }
-
-    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state != null) {
-            // Destroy the surface, unless it's the shared fullscreen dim.
-            if (state.dimLayer != mSharedFullScreenDimLayer) {
-                state.dimLayer.destroySurface();
-            }
-            mState.remove(dimLayerUser);
-        }
-        if (mState.isEmpty()) {
-            mSharedFullScreenDimLayer = null;
-        }
-    }
-
-    @VisibleForTesting
-    boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        return mState.containsKey(dimLayerUser);
-    }
-
-    @VisibleForTesting
-    boolean hasSharedFullScreenDimLayer() {
-        return mSharedFullScreenDimLayer != null;
-    }
-
-    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
-        applyDim(dimLayerUser, animator, false /* aboveApp */);
-    }
-
-    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
-        applyDim(dimLayerUser, animator, true /* aboveApp */);
-    }
-
-    void applyDim(
-            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
-        if (dimLayerUser == null) {
-            Slog.e(TAG, "Trying to apply dim layer for: " + this
-                    + ", but no dim layer user found.");
-            return;
-        }
-        if (!getContinueDimming(dimLayerUser)) {
-            setContinueDimming(dimLayerUser);
-            if (!isDimming(dimLayerUser, animator)) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
-                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
-            }
-        }
-    }
-
-    private static class DimLayerState {
-        // The particular window requesting a dim layer. If null, hide dimLayer.
-        WindowStateAnimator animator;
-        // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
-        // end then stop any dimming.
-        boolean continueDimming;
-        DimLayer dimLayer;
-        boolean dimAbove;
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DimLayerController");
-        final String doubleSpace = "  ";
-        final String prefixPlusDoubleSpace = prefix + doubleSpace;
-
-        for (int i = 0, n = mState.size(); i < n; i++) {
-            pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
-            DimLayerState state = mState.valueAt(i);
-            pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
-                    + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
-                    + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
-            if (state.dimLayer != null) {
-                state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
new file mode 100644
index 0000000..9fe16ae
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -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
+ */
+
+package com.android.server.wm;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.graphics.Rect;
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
+class Dimmer {
+    private static final String TAG = "WindowManager";
+
+    private class DimState {
+        SurfaceControl mSurfaceControl;
+        boolean mDimming;
+
+        /**
+         * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for
+         * details on Dim lifecycle.
+         */
+        boolean mDontReset;
+
+        DimState(SurfaceControl ctl) {
+            mSurfaceControl = ctl;
+            mDimming = true;
+        }
+    };
+
+    private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>();
+
+    /**
+     * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+     * host, some controller of it, or one of the hosts children.
+     */
+    private WindowContainer mHost;
+
+    Dimmer(WindowContainer host) {
+        mHost = host;
+    }
+
+    SurfaceControl makeDimLayer() {
+        final SurfaceControl control = mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer(true)
+                .setName("Dim Layer for - " + mHost.getName())
+                .build();
+        return control;
+    }
+
+    /**
+     * Retreive the DimState for a given child of the host.
+     */
+    DimState getDimState(WindowContainer container) {
+        DimState state = mDimLayerUsers.get(container);
+        if (state == null) {
+            final SurfaceControl ctl = makeDimLayer();
+            state = new DimState(ctl);
+            /**
+             * See documentation on {@link #dimAbove} to understand lifecycle management of Dim's
+             * via state resetting for Dim's with containers.
+             */
+            if (container == null) {
+                state.mDontReset = true;
+            }
+            mDimLayerUsers.put(container, state);
+        }
+        return state;
+    }
+
+    private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
+            float alpha) {
+        final DimState d = getDimState(container);
+        t.show(d.mSurfaceControl);
+        if (container != null) {
+            t.setRelativeLayer(d.mSurfaceControl,
+                    container.getSurfaceControl(), relativeLayer);
+        } else {
+            t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE);
+        }
+        t.setAlpha(d.mSurfaceControl, alpha);
+
+        d.mDimming = true;
+    }
+
+    /**
+     * Finish a dim started by dimAbove in the case there was no call to dimAbove.
+     *
+     * @param t A Transaction in which to finish the dim.
+     */
+    void stopDim(SurfaceControl.Transaction t) {
+        DimState d = getDimState(null);
+        t.hide(d.mSurfaceControl);
+        d.mDontReset = false;
+    }
+    /**
+     * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
+     * remove this effect. If the Dim can be assosciated with a particular child of the host
+     * consider using the other variant of dimAbove which ties the Dim lifetime to the child
+     * lifetime more explicitly.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param alpha The alpha at which to Dim.
+     */
+    void dimAbove(SurfaceControl.Transaction t, float alpha) {
+        dim(t, null, 1, alpha);
+    }
+
+    /**
+     * Place a dim above the given container, which should be a child of the host container.
+     * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
+     * and the child should call dimAbove again to request the Dim to continue.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param container The container which to dim above. Should be a child of our host.
+     * @param alpha The alpha at which to Dim.
+     */
+    void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+        dim(t, container, 1, alpha);
+    }
+
+    /**
+     * Like {@link #dimAbove} but places the dim below the given container.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param container The container which to dim below. Should be a child of our host.
+     * @param alpha The alpha at which to Dim.
+     */
+
+    void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+        dim(t, container, -1, alpha);
+    }
+
+    /**
+     * Mark all dims as pending completion on the next call to {@link #updateDims}
+     *
+     * This is intended for us by the host container, to be called at the beginning of
+     * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+     * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
+     * a chance to request dims to continue.
+     */
+    void resetDimStates() {
+        for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+            final DimState state = mDimLayerUsers.valueAt(i);
+            if (state.mDontReset == false) {
+                state.mDimming = false;
+            }
+        }
+    }
+
+    /**
+     * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
+     * described in {@link #resetDimStates}.
+     *
+     * @param t A transaction in which to update the dims.
+     * @param bounds The bounds at which to dim.
+     * @return true if any Dims were updated.
+     */
+    boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+        boolean didSomething = false;
+        for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+            DimState state = mDimLayerUsers.valueAt(i);
+            // TODO: We want to animate the addition and removal of Dim's instead of immediately
+            // acting. When we do this we need to take care to account for the "Replacing Windows"
+            // case (and seamless dim transfer).
+            if (state.mDimming == false) {
+                mDimLayerUsers.removeAt(i);
+                state.mSurfaceControl.destroy();
+            } else {
+                didSomething = true;
+                // TODO: Once we use geometry from hierarchy this falls away.
+                t.setSize(state.mSurfaceControl, bounds.width(), bounds.height());
+                t.setPosition(state.mSurfaceControl, bounds.left, bounds.top);
+            }
+        }
+        return didSomething;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4d839d0..17312b2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -142,8 +142,10 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.InputDevice;
+import android.view.MagnificationSpec;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.WindowManagerPolicy;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -321,8 +323,6 @@
     final DockedStackDividerController mDividerControllerLocked;
     final PinnedStackController mPinnedStackControllerLocked;
 
-    DimLayerController mDimLayerController;
-
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
 
     private boolean mHaveBootMsg = false;
@@ -346,10 +346,37 @@
     // {@code false} if this display is in the processing of being created.
     private boolean mDisplayReady = false;
 
-    private final WindowLayersController mLayersController;
     WallpaperController mWallpaperController;
     int mInputMethodAnimLayerAdjustment;
 
+    private final SurfaceSession mSession = new SurfaceSession();
+
+    /**
+     * We organize all top-level Surfaces in to the following layers.
+     * mOverlayLayer contains a few Surfaces which are always on top of others
+     * and omitted from Screen-Magnification ({@link WindowState#isScreenOverlay})
+     * {@link #mWindowingLayer} contains everything else.
+     */
+    private SurfaceControl mOverlayLayer;
+
+    /**
+     * See {@link #mOverlayLayer}
+     */
+    private SurfaceControl mWindowingLayer;
+
+    /**
+     * Specifies the size of the surfaces in {@link #mOverlayLayer} and {@link #mWindowingLayer}.
+     * <p>
+     * For these surfaces currently we use a surface based on the larger of width or height so we
+     * don't have to resize when rotating the display.
+     */
+    private int mSurfaceSize;
+
+    /**
+     * A list of surfaces to be destroyed after {@link #mPendingTransaction} is applied.
+     */
+    private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>();
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         if (winAnimator.hasSurface()) {
@@ -503,9 +530,6 @@
         return true;
     };
 
-    private final Consumer<WindowState> mPrepareWindowSurfaces =
-            w -> w.mWinAnimator.prepareSurfaceLocked(true);
-
     private final Consumer<WindowState> mPerformLayout = w -> {
         // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
         // wasting time and funky changes while a window is animating away.
@@ -558,12 +582,6 @@
                     w.updateLastInsetValues();
                 }
 
-                // Window frames may have changed. Update dim layer with the new bounds.
-                final Task task = w.getTask();
-                if (task != null) {
-                    mDimLayerController.updateDimLayer(task);
-                }
-
                 if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
                         + " mContainingFrame=" + w.mContainingFrame
                         + " mDisplayFrame=" + w.mDisplayFrame);
@@ -657,8 +675,6 @@
             }
         }
 
-        w.applyDimLayerIfNeeded();
-
         if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
                 && mWallpaperController.isWallpaperTarget(w)) {
             // This is the wallpaper target and its obscured state changed... make sure the
@@ -741,13 +757,11 @@
      * initialize direct children.
      * @param display May not be null.
      * @param service You know.
-     * @param layersController window layer controller used to assign layer to the windows on this
-     *                         display.
      * @param wallpaperController wallpaper windows controller used to adjust the positioning of the
      *                            wallpaper windows in the window list.
      */
     DisplayContent(Display display, WindowManagerService service,
-            WindowLayersController layersController, WallpaperController wallpaperController) {
+            WallpaperController wallpaperController) {
         if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                     + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -756,7 +770,6 @@
 
         mDisplay = display;
         mDisplayId = display.getDisplayId();
-        mLayersController = layersController;
         mWallpaperController = wallpaperController;
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
@@ -766,7 +779,22 @@
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service, this);
         mPinnedStackControllerLocked = new PinnedStackController(service, this);
-        mDimLayerController = new DimLayerController(this);
+
+        mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth);
+
+        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession)
+                .setSize(mSurfaceSize, mSurfaceSize)
+                .setOpaque(true);
+        mWindowingLayer = b.setName("Display Root").build();
+        mOverlayLayer = b.setName("Display Overlays").build();
+
+        getPendingTransaction().setLayer(mWindowingLayer, 0)
+                .setLayerStack(mWindowingLayer, mDisplayId)
+                .show(mWindowingLayer)
+                .setLayer(mOverlayLayer, 1)
+                .setLayerStack(mOverlayLayer, mDisplayId)
+                .show(mOverlayLayer);
+        getPendingTransaction().apply();
 
         // These are the only direct children we should ever have and they are permanent.
         super.addChild(mBelowAppWindowsContainers, null);
@@ -1030,11 +1058,7 @@
 
         setLayoutNeeded();
         final int[] anim = new int[2];
-        if (isDimming()) {
-            anim[0] = anim[1] = 0;
-        } else {
-            mService.mPolicy.selectRotationAnimationLw(anim);
-        }
+        mService.mPolicy.selectRotationAnimationLw(anim);
 
         if (!rotateSeamlessly) {
             mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this);
@@ -1071,8 +1095,7 @@
             //       it doesn't support hardware OpenGL emulation yet.
             if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
                     && screenRotationAnimation.hasScreenshot()) {
-                if (screenRotationAnimation.setRotationInTransaction(
-                        rotation, mService.mFxSession,
+                if (screenRotationAnimation.setRotationInTransaction(rotation,
                         MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
                         mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
                     mService.scheduleAnimationLocked();
@@ -1907,22 +1930,6 @@
         }
     }
 
-    boolean animateDimLayers() {
-        return mDimLayerController.animateDimLayers();
-    }
-
-    private void resetDimming() {
-        mDimLayerController.resetDimming();
-    }
-
-    boolean isDimming() {
-        return mDimLayerController.isDimming();
-    }
-
-    private void stopDimmingIfNeeded() {
-        mDimLayerController.stopDimmingIfNeeded();
-    }
-
     @Override
     void removeIfPossible() {
         if (isAnimating()) {
@@ -1938,7 +1945,6 @@
         try {
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
-            mDimLayerController.close();
             if (mService.canDispatchPointerEvents()) {
                 if (mTapDetector != null) {
                     mService.unregisterPointerEventListener(mTapDetector);
@@ -1947,6 +1953,9 @@
                     mService.unregisterPointerEventListener(mService.mMousePositionTracker);
                 }
             }
+            // The pending transaction won't be applied so we should
+            // just clean up any surfaces pending destruction.
+            onPendingTransactionApplied();
         } finally {
             mRemovingDisplay = false;
         }
@@ -2228,8 +2237,7 @@
                 token.dump(pw, "    ");
             }
         }
-        pw.println();
-        mDimLayerController.dump(prefix, pw);
+
         pw.println();
 
         // Dump stack references
@@ -2342,10 +2350,16 @@
 
     /** Updates the layer assignment of windows on this display. */
     void assignWindowLayers(boolean setLayoutNeeded) {
-        mLayersController.assignWindowLayers(this);
+        assignChildLayers(getPendingTransaction());
         if (setLayoutNeeded) {
             setLayoutNeeded();
         }
+
+        // We accumlate the layer changes in-to "getPendingTransaction()" but we defer
+        // the application of this transaction until the animation pass triggers
+        // prepareSurfaces. This allows us to synchronize Z-ordering changes with
+        // the hiding and showing of surfaces.
+        scheduleAnimation();
     }
 
     // TODO: This should probably be called any time a visual change is made to the hierarchy like
@@ -2701,10 +2715,6 @@
         }
     }
 
-    void prepareWindowSurfaces() {
-        forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */);
-    }
-
     boolean inputMethodClientHasFocus(IInputMethodClient client) {
         final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
         if (imFocus == null) {
@@ -2846,7 +2856,6 @@
         } while (pendingLayoutChanges != 0);
 
         mTmpApplySurfaceChangesTransactionState.reset();
-        resetDimming();
 
         mTmpRecoveringMemory = recoveringMemory;
         forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
@@ -2857,8 +2866,6 @@
                 mTmpApplySurfaceChangesTransactionState.preferredModeId,
                 true /* inTraversal, must call performTraversalInTrans... below */);
 
-        stopDimmingIfNeeded();
-
         final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
         if (wallpaperVisible != mLastWallpaperVisible) {
             mLastWallpaperVisible = wallpaperVisible;
@@ -3062,13 +3069,6 @@
                 // Include this window.
 
                 final WindowStateAnimator winAnim = w.mWinAnimator;
-                int layer = winAnim.mSurfaceController.getLayer();
-                if (mScreenshotApplicationState.maxLayer < layer) {
-                    mScreenshotApplicationState.maxLayer = layer;
-                }
-                if (mScreenshotApplicationState.minLayer > layer) {
-                    mScreenshotApplicationState.minLayer = layer;
-                }
 
                 // Don't include wallpaper in bounds calculation
                 if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
@@ -3112,8 +3112,6 @@
 
             final WindowState appWin = mScreenshotApplicationState.appWin;
             final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
-            final int maxLayer = mScreenshotApplicationState.maxLayer;
-            final int minLayer = mScreenshotApplicationState.minLayer;
 
             if (appToken != null && appWin == null) {
                 // Can't find a window to snapshot.
@@ -3134,11 +3132,6 @@
             // because we don't want to release the mWindowMap lock until the screenshot is
             // taken.
 
-            if (maxLayer == 0) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                        + ": returning null maxLayer=" + maxLayer);
-                return null;
-            }
 
             if (!mutableIncludeFullDisplay.value) {
                 // Constrain frame to the screen size.
@@ -3183,8 +3176,6 @@
             convertCropForSurfaceFlinger(crop, rot, dw, dh);
 
             if (DEBUG_SCREENSHOT) {
-                Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
-                        + maxLayer + " appToken=" + appToken);
                 forAllWindows(w -> {
                     final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
                     Slog.i(TAG_WM, w + ": " + w.mLayer
@@ -3206,11 +3197,13 @@
             SurfaceControl.openTransaction();
             SurfaceControl.closeTransactionSync();
 
-            bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
+            // TODO(b/68392460): We should screenshot Task controls directly
+            // but it's difficult at the moment as the Task doesn't have the
+            // correct size set.
+            bitmap = screenshoter.screenshot(crop, width, height, 0, 1,
                     inRotation, rot);
             if (bitmap == null) {
-                Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
-                        + ") to layer " + maxLayer);
+                Slog.w(TAG_WM, "Failed to take screenshot");
                 return null;
             }
         }
@@ -3366,6 +3359,10 @@
      * I.e Activities.
      */
     private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+        /**
+         * A control placed at the appropriate level for transitions to occur.
+         */
+        SurfaceControl mAnimationLayer = null;
 
         // Cached reference to some special stacks we tend to get a lot so we don't need to loop
         // through the list to find them.
@@ -3677,6 +3674,50 @@
             // to prevent freezing/unfreezing the display too early.
             return mLastOrientation;
         }
+
+        @Override
+        void assignChildLayers(SurfaceControl.Transaction t) {
+            final int NORMAL_STACK_STATE = 0;
+            final int BOOSTED_STATE = 1;
+            final int ALWAYS_ON_TOP_STATE = 2;
+
+            // We allow stacks to change visual order from the AM specified order due to
+            // Z-boosting during animations. However we must take care to ensure TaskStacks
+            // which are marked as alwaysOnTop remain that way.
+            int layer = 0;
+            for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
+                for (int i = 0; i < mChildren.size(); i++) {
+                    final TaskStack s = mChildren.get(i);
+                    layer++;
+                    if (state == NORMAL_STACK_STATE) {
+                        s.assignLayer(t, layer);
+                    } else if (state == BOOSTED_STATE && s.needsZBoost()) {
+                        s.assignLayer(t, layer);
+                    } else if (state == ALWAYS_ON_TOP_STATE &&
+                            s.isAlwaysOnTop()) {
+                        s.assignLayer(t, layer);
+                    }
+                    s.assignChildLayers(t);
+                }
+                // The appropriate place for App-Transitions to occur is right
+                // above all other animations but still below things in the Picture-and-Picture
+                // windowing mode.
+                if (state == BOOSTED_STATE && mAnimationLayer != null) {
+                    t.setLayer(mAnimationLayer, layer + 1);
+                }
+            }
+        }
+
+        @Override
+        void onParentSet() {
+            super.onParentSet();
+            if (getParent() != null) {
+                mAnimationLayer = makeSurface().build();
+            } else {
+                mAnimationLayer.destroy();
+                mAnimationLayer = null;
+            }
+        }
     }
 
     /**
@@ -3760,4 +3801,119 @@
         E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
                 boolean useIdentityTransform, int rotation);
     }
+
+    SurfaceControl.Builder makeSurface(SurfaceSession s) {
+        return mService.makeSurfaceBuilder(s)
+                .setParent(mWindowingLayer);
+    }
+
+    @Override
+    SurfaceSession getSession() {
+        return mSession;
+    }
+
+    @Override
+    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+        SurfaceSession s = child != null ? child.getSession() : getSession();
+        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(s);
+        b.setSize(mSurfaceSize, mSurfaceSize);
+
+        if (child == null) {
+            return b;
+        }
+
+        b.setName(child.getName());
+        if (child.isScreenOverlay()) {
+            return b.setParent(mOverlayLayer);
+        } else {
+            return b.setParent(mWindowingLayer);
+        }
+    }
+
+    /**
+     * The makeSurface variants are for use by the window-container
+     * hierarchy. makeOverlay here is a function for various non windowing
+     * overlays like the ScreenRotation screenshot, the Strict Mode Flash
+     * and other potpourii.
+     */
+    SurfaceControl.Builder makeOverlay() {
+        return mService.makeSurfaceBuilder(mSession)
+            .setParent(mOverlayLayer);
+    }
+
+    void applyMagnificationSpec(MagnificationSpec spec) {
+        applyMagnificationSpec(getPendingTransaction(), spec);
+        getPendingTransaction().apply();
+    }
+
+    @Override
+    void onParentSet() {
+        // Since we are the top of the SurfaceControl hierarchy here
+        // we create the root surfaces explicitly rather than chaining
+        // up as the default implementation in onParentSet does. So we
+        // explicitly do NOT call super here.
+    }
+
+    @Override
+    void assignChildLayers(SurfaceControl.Transaction t) {
+        t.setLayer(mOverlayLayer, 1)
+                .setLayer(mWindowingLayer, 0);
+
+        // These are layers as children of "mWindowingLayer"
+        mBelowAppWindowsContainers.assignLayer(t, 0);
+        mTaskStackContainers.assignLayer(t, 1);
+        mAboveAppWindowsContainers.assignLayer(t, 2);
+
+        WindowState imeTarget = mService.mInputMethodTarget;
+        if (imeTarget == null || imeTarget.inSplitScreenWindowingMode()) {
+            // In split-screen windowing mode we can't layer the
+            // IME relative to the IME target because it needs to
+            // go over the docked divider, so instead we place it on top
+            // of everything and use relative layering of windows which need
+            // to go above it (see special logic in WindowState#assignLayer)
+            mImeWindowsContainers.assignLayer(t, 3);
+        } else {
+            t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
+                    imeTarget.getSurfaceControl(),
+                    // TODO: We need to use an extra level on the app surface to ensure
+                    // this is always above SurfaceView but always below attached window.
+                    1);
+        }
+
+        // Above we have assigned layers to our children, now we ask them to assign
+        // layers to their children.
+        mBelowAppWindowsContainers.assignChildLayers(t);
+        mTaskStackContainers.assignChildLayers(t);
+        mAboveAppWindowsContainers.assignChildLayers(t);
+        mImeWindowsContainers.assignChildLayers(t);
+    }
+
+    /**
+     * Here we satisfy an unfortunate special case of the IME in split-screen mode. Imagine
+     * that the IME target is one of the docked applications. We'd like the docked divider to be
+     * above both of the applications, and we'd like the IME to be above the docked divider.
+     * However we need child windows of the applications to be above the IME (Text drag handles).
+     * This is a non-strictly hierarcical layering and we need to break out of the Z ordering
+     * somehow. We do this by relatively ordering children of the target to the IME in cooperation
+     * with {@link #WindowState#assignLayer}
+     */
+    void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
+        t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1);
+    }
+
+    @Override
+    void destroyAfterPendingTransaction(SurfaceControl surface) {
+        mPendingDestroyingSurfaces.add(surface);
+    }
+
+    /**
+     * Destroys any surfaces that have been put into the pending list with
+     * {@link #destroyAfterTransaction}.
+     */
+    void onPendingTransactionApplied() {
+        for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+            mPendingDestroyingSurfaces.get(i).destroy();
+        }
+        mPendingDestroyingSurfaces.clear();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index d79ba89..8308417 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -54,7 +54,6 @@
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.server.LocalServices;
-import com.android.server.wm.DimLayer.DimLayerUser;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
@@ -62,7 +61,7 @@
 /**
  * Keeps information about the docked stack divider.
  */
-public class DockedStackDividerController implements DimLayerUser {
+public class DockedStackDividerController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
 
@@ -114,7 +113,6 @@
     private boolean mLastVisibility = false;
     private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
             = new RemoteCallbackList<>();
-    private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
     private int mOriginalDockedSide = DOCKED_INVALID;
@@ -141,13 +139,12 @@
     private boolean mImeHideRequested;
     private final Rect mLastDimLayerRect = new Rect();
     private float mLastDimLayerAlpha;
+    private TaskStack mDimmedStack;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
         final Context context = service.mContext;
-        mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
-                "DockedStackDim");
         mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
                 context, android.R.interpolator.fast_out_slow_in);
         loadDimens();
@@ -463,6 +460,11 @@
         }
         mOriginalDockedSide = DOCKED_INVALID;
         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
+
+        if (mDimmedStack != null) {
+            mDimmedStack.stopDimming();
+            mDimmedStack = null;
+        }
     }
 
     /**
@@ -564,34 +566,12 @@
         final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
-            stack.getDimBounds(mTmpRect);
-            if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
-                if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
-                    try {
-                        // TODO: This should use the regular animation transaction - here and below
-                        mService.openSurfaceTransaction();
-                        mDimLayer.setBounds(mTmpRect);
-                        mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
-                    } finally {
-                        mService.closeSurfaceTransaction("setResizeDimLayer");
-                    }
-                }
-                mLastDimLayerRect.set(mTmpRect);
-                mLastDimLayerAlpha = alpha;
-            } else {
-                visibleAndValid = false;
-            }
+            mDimmedStack = stack;
+            stack.dim(alpha);
         }
-        if (!visibleAndValid) {
-            if (mLastDimLayerAlpha != 0f) {
-                try {
-                    mService.openSurfaceTransaction();
-                    mDimLayer.hide();
-                } finally {
-                    mService.closeSurfaceTransaction("setResizeDimLayer");
-                }
-            }
-            mLastDimLayerAlpha = 0f;
+        if (!visibleAndValid && stack != null) {
+            mDimmedStack = null;
+            stack.stopDimming();
         }
     }
 
@@ -829,12 +809,8 @@
             return animateForMinimizedDockedStack(now);
         } else if (mAnimatingForIme) {
             return animateForIme(now);
-        } else {
-            if (mDimLayer != null && mDimLayer.isDimming()) {
-                mDimLayer.setLayer(getResizeDimLayer());
-            }
-            return false;
         }
+        return false;
     }
 
     private boolean animateForIme(long now) {
@@ -942,27 +918,6 @@
                 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
     }
 
-    @Override
-    public boolean dimFullscreen() {
-        return false;
-    }
-
-    @Override
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayContent.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mDisplayContent != null;
-    }
-
-    @Override
-    public void getDimBounds(Rect outBounds) {
-        // This dim layer user doesn't need this.
-    }
-
-    @Override
     public String toShortString() {
         return TAG;
     }
@@ -977,10 +932,6 @@
         pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
         pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
         pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
-        if (mDimLayer.isDimming()) {
-            pw.println(prefix + "  Dim layer is dimming: ");
-            mDimLayer.printTo(prefix + "    ", pw);
-        }
     }
 
     void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 8bec8d7..fddf6ca 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -49,19 +49,19 @@
     private int mRotation;
     private boolean mVisible;
 
-    public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session,
+    public EmulatorDisplayOverlay(Context context, DisplayContent dc,
             int zOrder) {
+        final Display display = dc.getDisplay();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("EmulatorDisplayOverlay")
                     .setSize(mScreenSize.x, mScreenSize.y)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
-            ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
             ctrl.show();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index f541926..43dfccc 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -138,7 +138,6 @@
     ParcelFileDescriptor mSurfaceTraceFd;
     RemoteEventTrace mRemoteEventTrace;
 
-    private final WindowLayersController mLayersController;
     final WallpaperController mWallpaperController;
 
     private final Handler mHandler;
@@ -163,7 +162,6 @@
     RootWindowContainer(WindowManagerService service) {
         mService = service;
         mHandler = new MyHandler(service.mH.getLooper());
-        mLayersController = new WindowLayersController(mService);
         mWallpaperController = new WallpaperController(mService);
     }
 
@@ -231,7 +229,7 @@
     }
 
     private DisplayContent createDisplayContent(final Display display) {
-        final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+        final DisplayContent dc = new DisplayContent(display, mService,
                 mWallpaperController);
         final int displayId = display.getDisplayId();
 
@@ -1103,4 +1101,9 @@
     String getName() {
         return "ROOT";
     }
+
+    @Override
+    void scheduleAnimation() {
+        mService.scheduleAnimationLocked();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 3350fea..70bf15c 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -225,7 +225,7 @@
     }
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
-            SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
+            boolean inTransaction, boolean forceDefaultOrientation,
             boolean isSecure, WindowManagerService service) {
         mService = service;
         mContext = context;
@@ -269,7 +269,7 @@
 
         try {
             try {
-                mSurfaceControl = new SurfaceControl.Builder(session)
+                mSurfaceControl = displayContent.makeOverlay()
                         .setName("ScreenshotSurface")
                         .setSize(mWidth, mHeight)
                         .setSecure(isSecure)
@@ -281,7 +281,6 @@
                 // TODO(multidisplay): we should use the proper display
                 SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                         SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
-                mSurfaceControl.setLayerStack(display.getLayerStack());
                 mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
                 mSurfaceControl.setAlpha(0);
                 mSurfaceControl.show();
@@ -370,11 +369,11 @@
     }
 
     // Must be called while in a transaction.
-    public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+    public boolean setRotationInTransaction(int rotation,
             long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
         setRotationInTransaction(rotation);
         if (TWO_PHASE_ANIMATION) {
-            return startAnimation(session, maxAnimationDuration, animationScale,
+            return startAnimation(maxAnimationDuration, animationScale,
                     finalWidth, finalHeight, false, 0, 0);
         }
 
@@ -385,7 +384,7 @@
     /**
      * Returns true if animating.
      */
-    private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+    private boolean startAnimation(long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, boolean dismissing,
             int exitAnim, int enterAnim) {
         if (mSurfaceControl == null) {
@@ -561,8 +560,8 @@
                 Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
                         mOriginalWidth*2, mOriginalHeight*2);
                 Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
-                mCustomBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
+                mCustomBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false);
                 mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -601,8 +600,8 @@
                             mOriginalWidth*2, mOriginalHeight*2);
                     inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
                 }
-                mExitingBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
+                mExitingBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
                 mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -624,8 +623,8 @@
                 Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
                         finalWidth*2, finalHeight*2);
                 Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
+                mEnteringBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
@@ -642,7 +641,7 @@
     /**
      * Returns true if animating.
      */
-    public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+    public boolean dismiss(long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
         if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
         if (mSurfaceControl == null) {
@@ -650,7 +649,7 @@
             return false;
         }
         if (!mStarted) {
-            startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+            startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight,
                     true, exitAnim, enterAnim);
         }
         if (!mStarted) {
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index eb8ee69..f51a6a9 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -41,15 +41,14 @@
     private boolean mDrawNeeded;
     private final int mThickness = 20;
 
-    public StrictModeFlash(Display display, SurfaceSession session) {
+    public StrictModeFlash(DisplayContent dc) {
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("StrictModeFlash")
                     .setSize(1, 1)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
-            ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
             ctrl.setPosition(0, 0);
             ctrl.show();
diff --git a/core/java/android/os/IStatsCallbacks.aidl b/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
similarity index 62%
copy from core/java/android/os/IStatsCallbacks.aidl
copy to services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
index 02e7cd3..5390e5a 100644
--- a/core/java/android/os/IStatsCallbacks.aidl
+++ b/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package android.os;
+package com.android.server.wm;
 
-/**
-  * Callback for Statsd to allow binder calls to clients.
-  * {@hide}
-  */
-interface IStatsCallbacks {
-    void onReceiveLogs(out byte[] log);
-}
+import android.view.SurfaceSession;
+import android.view.SurfaceControl;
+
+interface SurfaceBuilderFactory {
+    SurfaceControl.Builder make(SurfaceSession s);
+};
+
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 13435d7..f70845e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -51,7 +51,7 @@
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> {
     static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
     // Return value from {@link setBounds} indicating no change was made to the Task bounds.
     private static final int BOUNDS_CHANGE_NONE = 0;
@@ -105,6 +105,9 @@
     // stack moves and we in fact do so when moving from full screen to pinned.
     private boolean mPreserveNonFloatingState = false;
 
+    private Dimmer mDimmer = new Dimmer(this);
+    private final Rect mTmpDimBoundsRect = new Rect();
+
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
             int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription,
             TaskWindowContainerController controller) {
@@ -188,12 +191,6 @@
         EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
         mDeferRemoval = false;
 
-        // Make sure to remove dim layer user first before removing task its from parent.
-        DisplayContent content = getDisplayContent();
-        if (content != null) {
-            content.mDimLayerController.removeDimLayerUser(this);
-        }
-
         super.removeImmediately();
     }
 
@@ -237,6 +234,8 @@
 
     @Override
     void onParentSet() {
+        super.onParentSet();
+
         // Update task bounds if needed.
         updateDisplayInfo(getDisplayContent());
 
@@ -312,9 +311,7 @@
         mBounds.set(bounds);
 
         mRotation = rotation;
-        if (displayContent != null) {
-            displayContent.mDimLayerController.updateDimLayer(this);
-        }
+
         onOverrideConfigurationChanged(overrideConfig);
         return boundsChange;
     }
@@ -482,7 +479,6 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    @Override
     public void getDimBounds(Rect out) {
         final DisplayContent displayContent = mStack.getDisplayContent();
         // It doesn't matter if we in particular are part of the resize, since we couldn't have
@@ -634,23 +630,6 @@
         return null;
     }
 
-    @Override
-    public boolean dimFullscreen() {
-        return isFullscreen();
-    }
-
-    @Override
-    public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) {
-        // If the dim layer is for a starting window, move the dim layer back in the z-order behind
-        // the lowest activity window to ensure it does not occlude the main window if it is
-        // translucent
-        final AppWindowToken appToken = animator.mWin.mAppToken;
-        if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) {
-            return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset);
-        }
-        return defaultLayer;
-    }
-
     boolean isFullscreen() {
         if (useCurrentBounds()) {
             return mFillsParent;
@@ -661,16 +640,6 @@
         return true;
     }
 
-    @Override
-    public DisplayInfo getDisplayInfo() {
-        return getDisplayContent().getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return getDisplayContent() != null;
-    }
-
     void forceWindowsScaleable(boolean force) {
         mService.openSurfaceTransaction();
         try {
@@ -718,9 +687,18 @@
         mPreserveNonFloatingState = false;
     }
 
+    Dimmer getDimmer() {
+        return mDimmer;
+    }
+
     @Override
-    public String toShortString() {
-        return "Task=" + mTaskId;
+    void prepareSurfaces() {
+        mDimmer.resetDimStates();
+        super.prepareSurfaces();
+        getDimBounds(mTmpDimBoundsRect);
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+            scheduleAnimation();
+        }
     }
 
     @CallSuper
@@ -757,4 +735,8 @@
             wtoken.dump(pw, triplePrefix);
         }
     }
+
+    String toShortString() {
+        return "Task=" + mTaskId;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 12f6b5a..5d4ba09 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -59,7 +59,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-class TaskPositioner implements DimLayer.DimLayerUser {
+class TaskPositioner {
     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
     private static final String TAG_LOCAL = "TaskPositioner";
     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -99,9 +99,6 @@
     private WindowPositionerEventReceiver mInputEventReceiver;
     private Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    private DimLayer mDimLayer;
-    @CtrlType
-    private int mCurrentDimSide;
     private Rect mTmpRect = new Rect();
     private int mSideMargin;
     private int mMinVisibleWidth;
@@ -207,15 +204,6 @@
                             mService.mActivityManager.resizeTask(
                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
                         }
-
-                        if (mCurrentDimSide != CTRL_NONE) {
-                            final int createMode = mCurrentDimSide == CTRL_LEFT
-                                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-                            mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
-                                    mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
-                                    null /* initialBounds */);
-                        }
                     } catch(RemoteException e) {}
 
                     // Post back to WM to handle clean-ups. We still need the input
@@ -243,7 +231,9 @@
     /**
      * @param display The Display that the window being dragged is on.
      */
-    void register(Display display) {
+    void register(DisplayContent displayContent) {
+        final Display display = displayContent.getDisplay();
+
         if (DEBUG_TASK_POSITIONING) {
             Slog.d(TAG, "Registering task positioner");
         }
@@ -305,7 +295,6 @@
         }
         mService.pauseRotationLocked();
 
-        mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
@@ -336,12 +325,6 @@
         mDragWindowHandle = null;
         mDragApplicationHandle = null;
         mDisplay = null;
-
-        if (mDimLayer != null) {
-            mDimLayer.destroySurface();
-            mDimLayer = null;
-        }
-        mCurrentDimSide = CTRL_NONE;
         mDragEnded = true;
 
         // Resume rotations after a drag.
@@ -434,7 +417,6 @@
         }
 
         updateWindowDragBounds(nX, nY, mTmpRect);
-        updateDimLayerVisibility(nX);
         return false;
     }
 
@@ -621,88 +603,6 @@
                 "updateWindowDragBounds: " + mWindowDragBounds);
     }
 
-    private void updateDimLayerVisibility(int x) {
-        @CtrlType
-        int dimSide = getDimSide(x);
-        if (dimSide == mCurrentDimSide) {
-            return;
-        }
-
-        mCurrentDimSide = dimSide;
-
-        if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
-        mService.openSurfaceTransaction();
-        if (mCurrentDimSide == CTRL_NONE) {
-            mDimLayer.hide();
-        } else {
-            showDimLayer();
-        }
-        mService.closeSurfaceTransaction("updateDimLayerVisibility");
-    }
-
-    /**
-     * Returns the side of the screen the dim layer should be shown.
-     * @param x horizontal coordinate used to determine if the dim layer should be shown
-     * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
-     * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
-     * shouldn't be shown.
-     */
-    private int getDimSide(int x) {
-        if (!mTask.mStack.inFreeformWindowingMode()
-                || !mTask.mStack.fillsParent()
-                || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
-            return CTRL_NONE;
-        }
-
-        mTask.mStack.getDimBounds(mTmpRect);
-        if (x - mSideMargin <= mTmpRect.left) {
-            return CTRL_LEFT;
-        }
-        if (x + mSideMargin >= mTmpRect.right) {
-            return CTRL_RIGHT;
-        }
-
-        return CTRL_NONE;
-    }
-
-    private void showDimLayer() {
-        mTask.mStack.getDimBounds(mTmpRect);
-        if (mCurrentDimSide == CTRL_LEFT) {
-            mTmpRect.right = mTmpRect.centerX();
-        } else if (mCurrentDimSide == CTRL_RIGHT) {
-            mTmpRect.left = mTmpRect.centerX();
-        }
-
-        mDimLayer.setBounds(mTmpRect);
-        mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
-                RESIZING_HINT_DURATION_MS);
-    }
-
-    @Override /** {@link DimLayer.DimLayerUser} */
-    public boolean dimFullscreen() {
-        return isFullscreen();
-    }
-
-    boolean isFullscreen() {
-        return false;
-    }
-
-    @Override /** {@link DimLayer.DimLayerUser} */
-    public DisplayInfo getDisplayInfo() {
-        return mTask.mStack.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mTask != null && mTask.getDisplayContent() != null;
-    }
-
-    @Override
-    public void getDimBounds(Rect out) {
-        // This dim layer user doesn't need this.
-    }
-
-    @Override
     public String toShortString() {
         return TAG;
     }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 053fb47..f9062a8 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -55,6 +55,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.SurfaceControl;
 
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -63,7 +64,7 @@
 
 import java.io.PrintWriter;
 
-public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements
         BoundsAnimationTarget {
     /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
      * restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -108,8 +109,8 @@
     /** Density as of last time {@link #mBounds} was set. */
     private int mDensity;
 
-    /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
-    private DimLayer mAnimationBackgroundSurface;
+    private SurfaceControl mAnimationBackgroundSurface;
+    private boolean mAnimationBackgroundSurfaceIsShown = false;
 
     /** The particular window with an Animation with non-zero background color. */
     private WindowStateAnimator mAnimationBackgroundAnimator;
@@ -149,6 +150,13 @@
 
     Rect mPreAnimationBounds = new Rect();
 
+    private Dimmer mDimmer = new Dimmer(this);
+
+    /**
+     * For {@link #prepareSurfaces}.
+     */
+    final Rect mTmpDimBoundsRect = new Rect();
+
     TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         mService = service;
         mStackId = stackId;
@@ -245,6 +253,35 @@
         }
     }
 
+    private void setAnimationBackgroundBounds(Rect bounds) {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().setSize(mAnimationBackgroundSurface, bounds.width(), bounds.height())
+                .setPosition(mAnimationBackgroundSurface, 0, 0);
+        scheduleAnimation();
+    }
+
+    private void hideAnimationSurface() {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().hide(mAnimationBackgroundSurface);
+        mAnimationBackgroundSurfaceIsShown = false;
+        scheduleAnimation();
+    }
+
+    private void showAnimationSurface(float alpha) {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
+                .setAlpha(mAnimationBackgroundSurface, alpha)
+                .show(mAnimationBackgroundSurface);
+        mAnimationBackgroundSurfaceIsShown = true;
+        scheduleAnimation();
+    }
+
     private boolean setBounds(Rect bounds) {
         boolean oldFullscreen = mFillsParent;
         int rotation = Surface.ROTATION_0;
@@ -267,10 +304,7 @@
             return false;
         }
 
-        if (mDisplayContent != null) {
-            mDisplayContent.mDimLayerController.updateDimLayer(this);
-            mAnimationBackgroundSurface.setBounds(bounds);
-        }
+        setAnimationBackgroundBounds(bounds);
 
         mBounds.set(bounds);
         mRotation = rotation;
@@ -368,7 +402,6 @@
     }
 
     /** Bounds of the stack with other system factors taken into consideration. */
-    @Override
     public void getDimBounds(Rect out) {
         getBounds(out);
     }
@@ -700,9 +733,12 @@
         }
 
         mDisplayContent = dc;
-        mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
-                "animation background stackId=" + mStackId);
+
         updateBoundsForWindowModeChange();
+        mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
+            .setName("animation background stackId=" + mStackId)
+            .build();
+
         super.onDisplayChanged(dc);
     }
 
@@ -914,16 +950,16 @@
 
     @Override
     void onParentSet() {
+        super.onParentSet();
+
         if (getParent() != null || mDisplayContent == null) {
             return;
         }
 
-        // Looks like the stack was removed from the display. Go ahead and clean things up.
-        mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
         if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.destroySurface();
+            mAnimationBackgroundSurface.destroy();
             mAnimationBackgroundSurface = null;
         }
 
@@ -933,9 +969,7 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.hide();
-        }
+        hideAnimationSurface();
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -944,8 +978,7 @@
                 || animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
             mAnimationBackgroundAnimator = winAnimator;
             animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
-            mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
-                    ((color >> 24) & 0xff) / 255f, 0);
+            showAnimationSurface(((color >> 24) & 0xff) / 255f);
         }
     }
 
@@ -1250,7 +1283,7 @@
         }
         proto.write(FILLS_PARENT, mFillsParent);
         mBounds.writeToProto(proto, BOUNDS);
-        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming());
+        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
         proto.end(token);
     }
 
@@ -1273,9 +1306,8 @@
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).dump(prefix + "  ", pw);
         }
-        if (mAnimationBackgroundSurface.isDimming()) {
-            pw.println(prefix + "mWindowAnimationBackgroundSurface:");
-            mAnimationBackgroundSurface.printTo(prefix + "  ", pw);
+        if (mAnimationBackgroundSurfaceIsShown) {
+            pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
         }
         if (!mExitingAppTokens.isEmpty()) {
             pw.println();
@@ -1299,11 +1331,6 @@
     }
 
     @Override
-    public boolean dimFullscreen() {
-        return !isActivityTypeStandard() || fillsParent();
-    }
-
-    @Override
     boolean fillsParent() {
         if (useCurrentBounds()) {
             return mFillsParent;
@@ -1315,16 +1342,6 @@
     }
 
     @Override
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayContent.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mDisplayContent != null;
-    }
-
-    @Override
     public String toString() {
         return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
     }
@@ -1333,7 +1350,6 @@
         return toShortString();
     }
 
-    @Override
     public String toShortString() {
         return "Stack=" + mStackId;
     }
@@ -1691,4 +1707,32 @@
                 || activityType == ACTIVITY_TYPE_RECENTS
                 || activityType == ACTIVITY_TYPE_ASSISTANT;
     }
+
+    Dimmer getDimmer() {
+        return mDimmer;
+    }
+
+    @Override
+    void prepareSurfaces() {
+        mDimmer.resetDimStates();
+        super.prepareSurfaces();
+        getDimBounds(mTmpDimBoundsRect);
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+            scheduleAnimation();
+        }
+    }
+
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayContent.getDisplayInfo();
+    }
+
+    void dim(float alpha) {
+        mDimmer.dimAbove(getPendingTransaction(), alpha);
+        scheduleAnimation();
+    }
+
+    void stopDimming() {
+        mDimmer.stopDim(getPendingTransaction());
+        scheduleAnimation();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index d97aaac..9216b66 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -53,7 +53,7 @@
     private int mLastDH;
     private boolean mDrawNeeded;
 
-    Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+    Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
         if (false) {
             Log.i(TAG_WM, "*********************** WATERMARK");
             for (int i=0; i<tokens.length; i++) {
@@ -61,7 +61,7 @@
             }
         }
 
-        mDisplay = display;
+        mDisplay = dc.getDisplay();
         mTokens = tokens;
 
         StringBuilder builder = new StringBuilder(32);
@@ -114,7 +114,7 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("WatermarkSurface")
                     .setSize(1, 1)
                     .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 1912095..20bade67 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -200,7 +200,7 @@
                     ++mAnimTransactionSequence;
                     dc.updateWindowsForAnimator(this);
                     dc.updateWallpaperForAnimator(this);
-                    dc.prepareWindowSurfaces();
+                    dc.prepareSurfaces();
                 }
 
                 for (int i = 0; i < numDisplays; i++) {
@@ -214,8 +214,6 @@
                     if (screenRotationAnimation != null) {
                         screenRotationAnimation.updateSurfacesInTransaction();
                     }
-
-                    orAnimating(dc.animateDimLayers());
                     orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
                     //TODO (multidisplay): Magnification is supported only for the default display.
                     if (accessibilityController != null && dc.isDefaultDisplay) {
@@ -237,6 +235,13 @@
                 if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
             }
 
+            final int numDisplays = mDisplayContentsAnimators.size();
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+                final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+                dc.onPendingTransactionApplied();
+            }
+
             boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
             boolean doRequest = false;
             if (mBulkUpdateParams != 0) {
@@ -271,6 +276,7 @@
             mService.destroyPreservedSurfaceLocked();
             mService.mWindowPlacerLocked.destroyPendingSurfaces();
 
+
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
                         + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8f4b897..a5e6288 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -21,9 +21,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
 import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
+import static android.view.SurfaceControl.Transaction;
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
+import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.util.Pools;
 
 import android.util.proto.ProtoOutputStream;
@@ -64,7 +68,14 @@
             new Pools.SynchronizedPool<>(3);
 
     // The owner/creator for this container. No controller if null.
-    private WindowContainerController mController;
+     WindowContainerController mController;
+
+    protected SurfaceControl mSurfaceControl;
+
+    /**
+     * Applied as part of the animation pass in "prepareSurfaces".
+     */
+    private Transaction mPendingTransaction = new Transaction();
 
     @Override
     final protected WindowContainer getParent() {
@@ -101,7 +112,22 @@
      * Supposed to be overridden and contain actions that should be executed after parent was set.
      */
     void onParentSet() {
-        // Do nothing by default.
+        if (mParent == null) {
+            return;
+        }
+        if (mSurfaceControl == null) {
+            // If we don't yet have a surface, but we now have a parent, we should
+            // build a surface.
+            mSurfaceControl = makeSurface().build();
+            getPendingTransaction().show(mSurfaceControl);
+        } else {
+            // If we have a surface but a new parent, we just need to perform a reparent.
+            getPendingTransaction().reparent(mSurfaceControl, mParent.mSurfaceControl.getHandle());
+        }
+
+        // Either way we need to ask the parent to assign us a Z-order.
+        mParent.assignChildLayers();
+        scheduleAnimation();
     }
 
     // Temp. holders for a chain of containers we are currently processing.
@@ -188,6 +214,11 @@
             mChildren.remove(child);
         }
 
+        if (mSurfaceControl != null) {
+            destroyAfterPendingTransaction(mSurfaceControl);
+            mSurfaceControl = null;
+        }
+
         if (mParent != null) {
             mParent.removeChild(this);
         }
@@ -195,6 +226,7 @@
         if (mController != null) {
             setController(null);
         }
+
     }
 
     /**
@@ -407,7 +439,7 @@
     }
 
     /**
-a     * Returns whether this child is on top of the window hierarchy.
+     * @return Whether this child is on top of the window hierarchy.
      */
     boolean isOnTop() {
         return getParent().getTopChild() == this && getParent().isOnTop();
@@ -673,6 +705,103 @@
         mController = controller;
     }
 
+    SurfaceControl.Builder makeSurface() {
+        final WindowContainer p = getParent();
+        return p.makeChildSurface(this);
+    }
+
+    /**
+     * @param child The WindowContainer this child surface is for, or null if the Surface
+     *              is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
+     */
+    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+        final WindowContainer p = getParent();
+        // Give the parent a chance to set properties. In hierarchy v1 we rely
+        // on this to set full-screen dimensions on all our Surface-less Layers.
+        final SurfaceControl.Builder b = p.makeChildSurface(child);
+        if (child != null && child.isScreenOverlay()) {
+            // If it's a screen overlay it's been promoted in the hierarchy (wrt to the
+            // WindowContainer hierarchy vs the SurfaceControl hierarchy)
+            // and we shouldn't set ourselves as the parent.
+            return b;
+        } else {
+            return b.setParent(mSurfaceControl);
+        }
+    }
+
+    /**
+     * There are various layers which require promotion from the WindowContainer
+     * hierarchy to the Overlay layer described in {@link DisplayContent}. See {@link WindowState}
+     * for the particular usage.
+     *
+     * TODO: Perhaps this should be eliminated, either through modifying
+     * the window container hierarchy or through modifying the way we express these overlay
+     * Surfaces (for example, the Magnification Overlay could be implemented like the Strict-mode
+     * Flash and not actually use a WindowState).
+     */
+    boolean isScreenOverlay() {
+        return false;
+    }
+
+    /**
+     * @return Whether this WindowContainer should be magnified by the accessibility magnifier.
+     */
+    boolean shouldMagnify() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            if (!mChildren.get(i).shouldMagnify()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    SurfaceSession getSession() {
+        if (getParent() != null) {
+            return getParent().getSession();
+        }
+        return null;
+    }
+
+    void assignLayer(Transaction t, int layer) {
+        if (mSurfaceControl != null) {
+            t.setLayer(mSurfaceControl, layer);
+        }
+    }
+
+    void assignChildLayers(Transaction t) {
+        int layer = 0;
+        boolean boosting = false;
+
+        // We use two passes as a way to promote children which
+        // need Z-boosting to the end of the list.
+        for (int i = 0; i < 2; i++ ) {
+            for (int j = 0; j < mChildren.size(); ++j) {
+                final WindowContainer wc = mChildren.get(j);
+                if (wc.needsZBoost() && !boosting) {
+                    continue;
+                }
+                wc.assignLayer(t, layer);
+                wc.assignChildLayers(t);
+
+                layer++;
+            }
+            boosting = true;
+        }
+    }
+
+    void assignChildLayers() {
+        assignChildLayers(getPendingTransaction());
+    }
+
+    boolean needsZBoost() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            if (mChildren.get(i).needsZBoost()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
@@ -719,4 +848,59 @@
             mConsumerWrapperPool.release(this);
         }
     }
+
+    // TODO(b/68336570): Should this really be on WindowContainer since it
+    // can only be used on the top-level nodes that aren't animated?
+    // (otherwise we would be fighting other callers of setMatrix).
+    void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
+        if (shouldMagnify()) {
+            t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
+                    .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+        } else {
+            for (int i = 0; i < mChildren.size(); i++) {
+                mChildren.get(i).applyMagnificationSpec(t, spec);
+            }
+        }
+    }
+
+    /**
+     * TODO: Once we totally eliminate global transaction we will pass transaction in here
+     * rather than merging to global.
+     */
+    void prepareSurfaces() {
+        SurfaceControl.mergeToGlobalTransaction(getPendingTransaction());
+        for (int i = 0; i < mChildren.size(); i++) {
+            mChildren.get(i).prepareSurfaces();
+        }
+    }
+
+    /**
+     * Trigger a call to prepareSurfaces from the animation thread, such that
+     * mPendingTransaction will be applied.
+     */
+    void scheduleAnimation() {
+        if (mParent != null) {
+            mParent.scheduleAnimation();
+        }
+    }
+
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    /**
+     * Destroy a given surface after executing mPendingTransaction. This is
+     * largely a workaround for destroy not being part of transactions
+     * rather than an intentional design, so please take care when
+     * expanding use.
+     */
+    void destroyAfterPendingTransaction(SurfaceControl surface) {
+        if (mParent != null) {
+            mParent.destroyAfterPendingTransaction(surface);
+        }
+    }
+    
+    Transaction getPendingTransaction() {
+        return mPendingTransaction;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
deleted file mode 100644
index 7caf2fe..0000000
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2015 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.util.Slog;
-
-import java.util.ArrayDeque;
-import java.util.function.Consumer;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
-
-/**
- * Controller for assigning layers to windows on the display.
- *
- * This class encapsulates general algorithm for assigning layers and special rules that we need to
- * apply on top. The general algorithm goes through windows from bottom to the top and the higher
- * the window is, the higher layer is assigned. The final layer is equal to base layer +
- * adjustment from the order. This means that the window list is assumed to be ordered roughly by
- * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be
- * handled with care, because they break the algorithm).
- *
- * On top of the general algorithm we add special rules, that govern such amazing things as:
- * <li>IME (which has higher base layer, but will be positioned above application windows)</li>
- * <li>docked/pinned windows (that need to be lifted above other application windows, including
- * animations)
- * <li>dock divider (which needs to live above applications, but below IME)</li>
- * <li>replaced windows, which need to live above their normal level, because they anticipate
- * an animation</li>.
- */
-class WindowLayersController {
-    private final WindowManagerService mService;
-
-    WindowLayersController(WindowManagerService service) {
-        mService = service;
-    }
-
-    private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>();
-    private WindowState mDockDivider = null;
-    private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
-    private int mCurBaseLayer;
-    private int mCurLayer;
-    private boolean mAnyLayerChanged;
-    private int mHighestApplicationLayer;
-    private int mHighestDockedAffectedLayer;
-    private int mHighestLayerInImeTargetBaseLayer;
-    private WindowState mImeTarget;
-    private boolean mAboveImeTarget;
-    private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
-
-    private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> {
-        boolean layerChanged = false;
-
-        int oldLayer = w.mLayer;
-        if (w.mBaseLayer == mCurBaseLayer) {
-            mCurLayer += WINDOW_LAYER_MULTIPLIER;
-        } else {
-            mCurBaseLayer = mCurLayer = w.mBaseLayer;
-        }
-        assignAnimLayer(w, mCurLayer);
-
-        // TODO: Preserved old behavior of code here but not sure comparing oldLayer to
-        // mAnimLayer and mLayer makes sense...though the worst case would be unintentional
-        // layer reassignment.
-        if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
-            layerChanged = true;
-            mAnyLayerChanged = true;
-        }
-
-        if (w.mAppToken != null) {
-            mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-        if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
-            mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-        if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) {
-            mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-
-        collectSpecialWindows(w);
-
-        if (layerChanged) {
-            w.scheduleAnimationIfDimming();
-        }
-    };
-
-    final void assignWindowLayers(DisplayContent dc) {
-        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
-                new RuntimeException("here").fillInStackTrace());
-
-        reset();
-        dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */);
-
-        adjustSpecialWindows();
-
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && mAnyLayerChanged
-                && dc.getDisplayId() == DEFAULT_DISPLAY) {
-            mService.mAccessibilityController.onWindowLayersChangedLocked();
-        }
-
-        if (DEBUG_LAYERS) logDebugLayers(dc);
-    }
-
-    private void logDebugLayers(DisplayContent dc) {
-        dc.forAllWindows((w) -> {
-            final WindowStateAnimator winAnimator = w.mWinAnimator;
-            Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer
-                    + " mLayer=" + w.mLayer + (w.mAppToken == null
-                    ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment())
-                    + " =mAnimLayer=" + winAnimator.mAnimLayer);
-        }, false /* traverseTopToBottom */);
-    }
-
-    private void reset() {
-        mPinnedWindows.clear();
-        mInputMethodWindows.clear();
-        mDockedWindows.clear();
-        mAssistantWindows.clear();
-        mReplacingWindows.clear();
-        mDockDivider = null;
-
-        mCurBaseLayer = 0;
-        mCurLayer = 0;
-        mAnyLayerChanged = false;
-
-        mHighestApplicationLayer = 0;
-        mHighestDockedAffectedLayer = 0;
-        mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
-        mImeTarget = mService.mInputMethodTarget;
-        mAboveImeTarget = false;
-        mAboveImeTargetAppWindows.clear();
-    }
-
-    private void collectSpecialWindows(WindowState w) {
-        if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
-            mDockDivider = w;
-            return;
-        }
-        if (w.mWillReplaceWindow) {
-            mReplacingWindows.add(w);
-        }
-        if (w.mIsImWindow) {
-            mInputMethodWindows.add(w);
-            return;
-        }
-        if (mImeTarget != null) {
-            if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
-                // Child windows of the ime target with a positive sub-layer should be placed above
-                // the IME.
-                mAboveImeTargetAppWindows.add(w);
-            } else if (mAboveImeTarget && w.mAppToken != null) {
-                // windows of apps above the IME target should be placed above the IME.
-                mAboveImeTargetAppWindows.add(w);
-            }
-            if (w == mImeTarget) {
-                mAboveImeTarget = true;
-            }
-        }
-
-        final int windowingMode = w.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_PINNED) {
-            mPinnedWindows.add(w);
-        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            mDockedWindows.add(w);
-        }
-        if (w.isActivityTypeAssistant()) {
-            mAssistantWindows.add(w);
-        }
-    }
-
-    private void adjustSpecialWindows() {
-        // The following adjustments are beyond the highest docked-affected layer
-        int layer = mHighestDockedAffectedLayer +  TYPE_LAYER_OFFSET;
-
-        // Adjust the docked stack windows and dock divider above only the windows that are affected
-        // by the docked stack. When this happens, also boost the assistant window layers, otherwise
-        // the docked stack windows & divider would be promoted above the assistant.
-        if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) {
-            while (!mDockedWindows.isEmpty()) {
-                final WindowState window = mDockedWindows.remove();
-                layer = assignAndIncreaseLayerIfNeeded(window, layer);
-            }
-
-            layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
-
-            while (!mAssistantWindows.isEmpty()) {
-                final WindowState window = mAssistantWindows.remove();
-                if (window.mLayer > mHighestDockedAffectedLayer) {
-                    layer = assignAndIncreaseLayerIfNeeded(window, layer);
-                }
-            }
-        }
-
-        // The following adjustments are beyond the highest app layer or boosted layer
-        layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER);
-
-        // We know that we will be animating a relaunching window in the near future, which will
-        // receive a z-order increase. We want the replaced window to immediately receive the same
-        // treatment, e.g. to be above the dock divider.
-        while (!mReplacingWindows.isEmpty()) {
-            layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
-        }
-
-        while (!mPinnedWindows.isEmpty()) {
-            layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
-        }
-
-        // Make sure IME is the highest window in the base layer of it's target.
-        if (mImeTarget != null) {
-            if (mImeTarget.mAppToken == null) {
-                // For non-app ime targets adjust the layer we start from to match what we found
-                // when assigning layers. Otherwise, just use the highest app layer we have some far.
-                layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER;
-            }
-
-            while (!mInputMethodWindows.isEmpty()) {
-                layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
-            }
-
-            // Adjust app windows the should be displayed above the IME since they are above the IME
-            // target.
-            while (!mAboveImeTargetAppWindows.isEmpty()) {
-                layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
-            }
-        }
-
-    }
-
-    private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
-        if (win != null) {
-            assignAnimLayer(win, layer);
-            // Make sure we leave space in-between normal windows for dims and such.
-            layer += WINDOW_LAYER_MULTIPLIER;
-        }
-        return layer;
-    }
-
-    private void assignAnimLayer(WindowState w, int layer) {
-        w.mLayer = layer;
-        w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
-                + w.getSpecialWindowAnimLayerAdjustment();
-        if (w.mAppToken != null) {
-            w.mAppToken.mAppAnimator.updateThumbnailLayer();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ce34306..e123bef 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -529,7 +529,6 @@
 
     AccessibilityController mAccessibilityController;
 
-    final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
     CircularDisplayMask mCircularDisplayMask;
@@ -804,6 +803,13 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
+    class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory {
+        public SurfaceControl.Builder make(SurfaceSession s) {
+            return new SurfaceControl.Builder(s);
+        }
+    };
+    SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory();
+
     static void boostPriorityForLockedSection() {
         sThreadPriorityBooster.boost();
     }
@@ -992,7 +998,6 @@
             mPointerEventDispatcher = null;
         }
 
-        mFxSession = new SurfaceSession();
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mDisplays = mDisplayManager.getDisplays();
         for (Display display : mDisplays) {
@@ -2478,11 +2483,8 @@
                 mWaitingForConfig = true;
                 displayContent.setLayoutNeeded();
                 int anim[] = new int[2];
-                if (displayContent.isDimming()) {
-                    anim[0] = anim[1] = 0;
-                } else {
-                    mPolicy.selectRotationAnimationLw(anim);
-                }
+                mPolicy.selectRotationAnimationLw(anim);
+
                 startFreezingDisplayLocked(false, anim[0], anim[1], displayContent);
                 config = new Configuration(mTempConfiguration);
             }
@@ -3592,8 +3594,7 @@
                                 com.android.internal.R.dimen.circular_display_mask_thickness);
 
                         mCircularDisplayMask = new CircularDisplayMask(
-                                getDefaultDisplayContentLocked().getDisplay(),
-                                mFxSession,
+                                getDefaultDisplayContentLocked(),
                                 mPolicy.getWindowLayerFromTypeLw(
                                         WindowManager.LayoutParams.TYPE_POINTER)
                                         * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
@@ -3621,8 +3622,7 @@
                 if (mEmulatorDisplayOverlay == null) {
                     mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
                             mContext,
-                            getDefaultDisplayContentLocked().getDisplay(),
-                            mFxSession,
+                            getDefaultDisplayContentLocked(),
                             mPolicy.getWindowLayerFromTypeLw(
                                     WindowManager.LayoutParams.TYPE_POINTER)
                                     * TYPE_LAYER_MULTIPLIER + 10);
@@ -3670,7 +3670,7 @@
                 // TODO(multi-display): support multiple displays
                 if (mStrictModeFlash == null) {
                     mStrictModeFlash = new StrictModeFlash(
-                            getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+                            getDefaultDisplayContentLocked());
                 }
                 mStrictModeFlash.setVisibility(on);
             } finally {
@@ -4522,7 +4522,7 @@
 
         Display display = displayContent.getDisplay();
         mTaskPositioner = new TaskPositioner(this);
-        mTaskPositioner.register(display);
+        mTaskPositioner.register(displayContent);
         mInputMonitor.updateInputWindowsLw(true /*force*/);
 
         // We need to grab the touch focus so that the touch events during the
@@ -5892,7 +5892,7 @@
 
             displayContent.updateDisplayInfo();
             screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
-                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
+                    inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
                     this);
             mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
                     screenRotationAnimation);
@@ -5952,11 +5952,10 @@
             // TODO(multidisplay): rotation on main screen only.
             DisplayInfo displayInfo = displayContent.getDisplayInfo();
             // Get rotation animation again, with new top window
-            boolean isDimming = displayContent.isDimming();
-            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
                 mExitAnimId = mEnterAnimId = 0;
             }
-            if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+            if (screenRotationAnimation.dismiss(MAX_ANIMATION_DURATION,
                     getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                         displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                 scheduleAnimationLocked();
@@ -6040,8 +6039,8 @@
                 if (toks != null && toks.length > 0) {
                     // TODO(multi-display): Show watermarks on secondary displays.
                     final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    mWatermark = new Watermark(displayContent.getDisplay(),
-                            displayContent.mRealDisplayMetrics, mFxSession, toks);
+                    mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+                            toks);
                 }
             }
         } catch (FileNotFoundException e) {
@@ -7456,6 +7455,11 @@
         public void registerDragDropControllerCallback(IDragDropCallback callback) {
             mDragDropController.registerCallback(callback);
         }
+
+        @Override
+        public void lockNow() {
+            WindowManagerService.this.lockNow(null);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
@@ -7557,4 +7561,13 @@
             w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
         }, false /* traverseTopToBottom */);
     }
+
+    public void applyMagnificationSpec(MagnificationSpec spec) {
+        getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+    }
+
+    SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
+        return mSurfaceBuilderFactory.make(s);
+    }
 }
+
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6b1932d..52b7a25 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.SurfaceControl.Transaction;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -41,6 +42,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -54,6 +56,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
@@ -147,6 +152,8 @@
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowInfo;
@@ -602,6 +609,14 @@
                 };
             };
 
+    /**
+     * Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host
+     * container.
+     */
+    private boolean mIsDimming = false;
+
+    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
@@ -2034,23 +2049,6 @@
         return isVisibleOrAdding();
     }
 
-    void scheduleAnimationIfDimming() {
-        final DisplayContent dc = getDisplayContent();
-        if (dc == null) {
-            return;
-        }
-
-        // If layout is currently deferred, we want to hold of with updating the layers.
-        if (mService.mWindowPlacerLocked.isLayoutDeferred()) {
-            return;
-        }
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
-            // Force an animation pass just to update the mDimLayer layer.
-            mService.scheduleAnimationLocked();
-        }
-    }
-
     private final class DeadWindowEventReceiver extends InputEventReceiver {
         DeadWindowEventReceiver(InputChannel inputChannel) {
             super(inputChannel, mService.mH.getLooper());
@@ -2106,31 +2104,12 @@
         mInputWindowHandle.inputChannel = null;
     }
 
-    void applyDimLayerIfNeeded() {
-        // When the app is terminated (eg. from Recents), the task might have already been
-        // removed with the window pending removal. Don't apply dim in such cases, as there
-        // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
-        final AppWindowToken token = mAppToken;
-        if (token != null && token.removed) {
-            return;
-        }
-
-        final DisplayContent dc = getDisplayContent();
-        if (!mAnimatingExit && mAppDied) {
-            // If app died visible, apply a dim over the window to indicate that it's inactive
-            dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
-        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
-                && dc != null && !mAnimatingExit && isVisible()) {
-            dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
-        }
-    }
-
-    private DimLayer.DimLayerUser getDimLayerUser() {
+    private Dimmer getDimmer() {
         Task task = getTask();
         if (task != null) {
-            return task;
+            return task.getDimmer();
         }
-        return getStack();
+        return getStack().getDimmer();
     }
 
     /** Returns true if the replacement window was removed. */
@@ -2152,9 +2131,6 @@
 
     private void removeReplacedWindow() {
         if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
-        if (isDimming()) {
-            transferDimToReplacement();
-        }
         mWillReplaceWindow = false;
         mAnimateReplacingWindow = false;
         mReplacingRemoveRequested = false;
@@ -2217,11 +2193,11 @@
             // need to intercept touches outside of that window. The dim layer user
             // associated with the window (task or stack) will give us the good bounds, as
             // they would be used to display the dim layer.
-            final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-            if (dimLayerUser != null) {
-                dimLayerUser.getDimBounds(mTmpRect);
+            final Task task = getTask();
+            if (task != null) {
+                task.getDimBounds(mTmpRect);
             } else {
-                getVisibleBounds(mTmpRect);
+                getStack().getDimBounds(mTmpRect);
             }
             if (inFreeformWindowingMode()) {
                 // For freeform windows we the touch region to include the whole surface for the
@@ -2729,14 +2705,6 @@
         return displayContent.isDefaultDisplay;
     }
 
-    @Override
-    public boolean isDimming() {
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        final DisplayContent dc = getDisplayContent();
-        return dimLayerUser != null && dc != null
-                && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
-    }
-
     void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
         mShowToOwnerOnly = showToOwnerOnly;
     }
@@ -3071,7 +3039,7 @@
         if (task == null) {
             return false;
         }
-        if (!inSplitScreenWindowingMode()) {
+        if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
             return false;
         }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3591,15 +3559,6 @@
         return winY;
     }
 
-    private void transferDimToReplacement() {
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        final DisplayContent dc = getDisplayContent();
-        if (dimLayerUser != null && dc != null) {
-            dc.mDimLayerController.applyDim(dimLayerUser,
-                    mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
-        }
-    }
-
     // During activity relaunch due to resize, we sometimes use window replacement
     // for only child windows (as the main window is handled by window preservation)
     // and the big surface.
@@ -4388,4 +4347,80 @@
             return false;
         }
     }
+
+    @Override
+    boolean shouldMagnify() {
+        if (mAttrs.type == TYPE_INPUT_METHOD ||
+                mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
+            return false;
+        } else if (isScreenOverlay()) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    boolean isScreenOverlay() {
+        // It's tempting to wonder: Have we forgotten the rounded corners overlay?
+        // worry not: it's a fake TYPE_NAVIGATION_BAR.
+        if (mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
+                mAttrs.type == TYPE_NAVIGATION_BAR ||
+                mAttrs.type == TYPE_STATUS_BAR) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    SurfaceSession getSession() {
+        if (mSession.mSurfaceSession != null) {
+            return mSession.mSurfaceSession;
+        } else {
+            return getParent().getSession();
+        }
+    }
+
+    @Override
+    boolean needsZBoost() {
+        return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+    }
+
+    @Override
+    SurfaceControl.Builder makeSurface() {
+        return mToken.makeChildSurface(this);
+    }
+
+
+    @Override
+    void prepareSurfaces() {
+        mIsDimming = false;
+        if (!mAnimatingExit && mAppDied) {
+            mIsDimming = true;
+            getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
+        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+                && !mAnimatingExit && isVisible()) {
+            mIsDimming = true;
+            getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
+        }
+
+        mWinAnimator.prepareSurfaceLocked(true);
+        super.prepareSurfaces();
+    }
+
+    @Override
+    void assignLayer(Transaction t, int layer) {
+        // See comment in assignRelativeLayerForImeTargetChild
+        if (!isChildWindow()
+                || (mService.mInputMethodTarget != getParentWindow())
+                || !inSplitScreenWindowingMode()) {
+            super.assignLayer(t, layer);
+            return;
+        }
+        getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
+    }
+
+    @Override
+    public boolean isDimming() {
+        return mIsDimming;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 86397ae..840cc40 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -60,7 +60,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
-import android.view.MagnificationSpec;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -559,7 +558,10 @@
         }
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false);
         if (mSurfaceController != null) {
-            mSurfaceController.setLayer(mAnimLayer + 1);
+            // Our SurfaceControl is always at layer 0 within the parent Surface managed by
+            // window-state. We want this old Surface to stay on top of the new one
+            // until we do the swap, so we place it at layer 1.
+            mSurfaceController.mSurfaceControl.setLayer(1);
         }
         mDestroyPreservedSurfaceUponRedraw = true;
         mSurfaceDestroyDeferred = true;
@@ -730,7 +732,6 @@
         try {
             mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false);
             mSurfaceController.setLayerStackInTransaction(getLayerStack());
-            mSurfaceController.setLayer(mAnimLayer);
         } finally {
             mService.closeSurfaceTransaction("createSurfaceLocked");
         }
@@ -867,22 +868,6 @@
         mPendingDestroySurface = null;
     }
 
-    void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) {
-        final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left;
-        final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top;
-
-        if (spec != null && !spec.isNop()) {
-            float scale = spec.scale;
-            transform.postScale(scale, scale);
-            transform.postTranslate(spec.offsetX, spec.offsetY);
-
-            // As we are scaling the whole surface, to keep the content
-            // in the same position we will also have to scale the surfaceInsets.
-            transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft),
-                    -(surfaceInsetTop*scale - surfaceInsetTop));
-        }
-    }
-
     void computeShownFrameLocked() {
         final boolean selfTransformation = mHasLocalTransformation;
         Transformation attachedTransformation =
@@ -969,11 +954,6 @@
                 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
             }
 
-            MagnificationSpec spec = getMagnificationSpec();
-            if (spec != null) {
-                applyMagnificationSpec(spec, tmpMatrix);
-            }
-
             // "convert" it into SurfaceFlinger's format
             // (a 2x2 matrix + an offset)
             // Here we must not transform the position of the surface
@@ -1057,49 +1037,16 @@
                 TAG, "computeShownFrameLocked: " + this +
                 " not attached, mAlpha=" + mAlpha);
 
-        MagnificationSpec spec = getMagnificationSpec();
-        if (spec != null) {
-            final Rect frame = mWin.mFrame;
-            final float tmpFloats[] = mService.mTmpFloats;
-            final Matrix tmpMatrix = mWin.mTmpMatrix;
-
-            tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
-            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
-
-            applyMagnificationSpec(spec, tmpMatrix);
-
-            tmpMatrix.getValues(tmpFloats);
-
-            mHaveMatrix = true;
-            mDsDx = tmpFloats[Matrix.MSCALE_X];
-            mDtDx = tmpFloats[Matrix.MSKEW_Y];
-            mDtDy = tmpFloats[Matrix.MSKEW_X];
-            mDsDy = tmpFloats[Matrix.MSCALE_Y];
-            float x = tmpFloats[Matrix.MTRANS_X];
-            float y = tmpFloats[Matrix.MTRANS_Y];
-            mWin.mShownPosition.set(Math.round(x), Math.round(y));
-
-            mShownAlpha = mAlpha;
-        } else {
-            mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
-            if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
-                mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
-            }
-            mShownAlpha = mAlpha;
-            mHaveMatrix = false;
-            mDsDx = mWin.mGlobalScale;
-            mDtDx = 0;
-            mDtDy = 0;
-            mDsDy = mWin.mGlobalScale;
+        mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
+        if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+            mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
         }
-    }
-
-    private MagnificationSpec getMagnificationSpec() {
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) {
-            return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
-        }
-        return null;
+        mShownAlpha = mAlpha;
+        mHaveMatrix = false;
+        mDsDx = mWin.mGlobalScale;
+        mDtDx = 0;
+        mDtDy = 0;
+        mDsDy = mWin.mGlobalScale;
     }
 
     /**
@@ -1140,26 +1087,6 @@
             w.expandForSurfaceInsets(finalClipRect);
         }
 
-        // We may be applying a magnification spec to all windows,
-        // simulating a transformation in screen space, in which case
-        // we need to transform all other screen space values...including
-        // the final crop. This is kind of messed up and we should look
-        // in to actually transforming screen-space via a parent-layer.
-        // b/38322835
-        MagnificationSpec spec = getMagnificationSpec();
-        if (spec != null && !spec.isNop()) {
-            Matrix transform = mWin.mTmpMatrix;
-            RectF finalCrop = mService.mTmpRectF;
-            transform.reset();
-            transform.postScale(spec.scale, spec.scale);
-            transform.postTranslate(-spec.offsetX, -spec.offsetY);
-            transform.mapRect(finalCrop);
-            finalClipRect.top = (int) finalCrop.top;
-            finalClipRect.left = (int) finalCrop.left;
-            finalClipRect.right = (int) finalCrop.right;
-            finalClipRect.bottom = (int) finalCrop.bottom;
-        }
-
         return true;
     }
 
@@ -1517,7 +1444,6 @@
             mReportSurfaceResized = true;
             mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                     WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-            w.applyDimLayerIfNeeded();
         }
     }
 
@@ -1615,7 +1541,6 @@
                         mDtDy * w.mHScale * mExtraHScale,
                         mDsDy * w.mVScale * mExtraVScale,
                         recoveringMemory);
-            mSurfaceController.setLayer(mAnimLayer);
 
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index a214523..6746754 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@
 
     final WindowStateAnimator mAnimator;
 
-    private SurfaceControlWithBackground mSurfaceControl;
+    SurfaceControlWithBackground mSurfaceControl;
 
     // Should only be set from within setShown().
     private boolean mSurfaceShown = false;
@@ -101,7 +101,8 @@
         mWindowSession = win.mSession;
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
-        final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+        final SurfaceControl.Builder b = win.makeSurface()
+                .setParent(win.getSurfaceControl())
                 .setName(name)
                 .setSize(w, h)
                 .setFormat(format)
@@ -245,25 +246,6 @@
         }
     }
 
-    void setLayer(int layer) {
-        if (mSurfaceControl != null) {
-            mService.openSurfaceTransaction();
-            try {
-                if (mAnimator.mWin.usesRelativeZOrdering()) {
-                    mSurfaceControl.setRelativeLayer(
-                            mAnimator.mWin.getParentWindow()
-                            .mWinAnimator.mSurfaceController.mSurfaceControl,
-                            -1);
-                } else {
-                    mSurfaceLayer = layer;
-                    mSurfaceControl.setLayer(layer);
-                }
-            } finally {
-                mService.closeSurfaceTransaction("setLayer");
-            }
-        }
-    }
-
     void setLayerStackInTransaction(int layerStack) {
         if (mSurfaceControl != null) {
             mSurfaceControl.setLayerStack(layerStack);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index cd5e475..5081868 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -704,7 +704,7 @@
 
             // Create a new surface for the thumbnail
             WindowState window = appToken.findMainWindow();
-            final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+            final SurfaceControl surfaceControl = appToken.makeSurface()
                     .setName("thumbnail anim")
                     .setSize(dirty.width(), dirty.height())
                     .setFormat(PixelFormat.TRANSLUCENT)
@@ -712,7 +712,6 @@
                             window != null ? window.mOwnerUid : Binder.getCallingUid())
                     .build();
 
-            surfaceControl.setLayerStack(display.getLayerStack());
             if (SHOW_TRANSACTIONS) {
                 Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
             }
@@ -750,10 +749,13 @@
             anim.restrictDuration(MAX_ANIMATION_DURATION);
             anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
 
-            openingAppAnimator.updateThumbnailLayer();
             openingAppAnimator.thumbnail = surfaceControl;
             openingAppAnimator.thumbnailAnimation = anim;
             mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+
+            // We parent the thumbnail to the app token, and just place it
+            // on top of anything else in the app token.
+            surfaceControl.setLayer(Integer.MAX_VALUE);
         } catch (Surface.OutOfResourcesException e) {
             Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
                     + dirty.width() + " h=" + dirty.height(), e);
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 5657f6c..6aa5101 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -67,6 +67,7 @@
             mWriteQueue.clear();
             mTraceFile.delete();
             try (OutputStream os = new FileOutputStream(mTraceFile)) {
+                mTraceFile.setReadable(true, false);
                 ProtoOutputStream proto = new ProtoOutputStream(os);
                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
                 proto.flush();
@@ -147,7 +148,7 @@
     }
 
     static WindowTracing createDefaultAndStartLooper(Context context) {
-        File file = new File("/data/system/window_trace.proto");
+        File file = new File("/data/misc/wmtrace/wm_trace.pb");
         WindowTracing windowTracing = new WindowTracing(file);
         new Thread(windowTracing::loop, "window_tracing").start();
         return windowTracing;
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 36e9e7f..4a2da37 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -23,7 +23,6 @@
     $(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \
     $(LOCAL_REL_DIR)/com_android_server_lights_LightsService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
     $(LOCAL_REL_DIR)/com_android_server_locksettings_SyntheticPasswordManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp
deleted file mode 100644
index ad372de..0000000
--- a/services/core/jni/com_android_server_location_ContextHubService.cpp
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- * Copyright 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.
- */
-
-#undef LOG_NDEBUG
-#undef LOG_TAG
-#define LOG_NDEBUG 0
-#define LOG_TAG "ContextHubService"
-
-#include <inttypes.h>
-#include <jni.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/endian.h>
-
-#include <chrono>
-#include <mutex>
-#include <queue>
-#include <unordered_map>
-#include <utility>
-
-#include <android-base/macros.h>
-#include <android/hardware/contexthub/1.0/IContexthub.h>
-#include <cutils/log.h>
-
-#include "core_jni_helpers.h"
-#include <nativehelper/JNIHelp.h>
-
-using android::hardware::contexthub::V1_0::AsyncEventType;
-using android::hardware::contexthub::V1_0::ContextHub;
-using android::hardware::contexthub::V1_0::ContextHubMsg;
-using android::hardware::contexthub::V1_0::HubAppInfo;
-using android::hardware::contexthub::V1_0::IContexthub;
-using android::hardware::contexthub::V1_0::IContexthubCallback;
-using android::hardware::contexthub::V1_0::NanoAppBinary;
-using android::hardware::contexthub::V1_0::Result;
-using android::hardware::contexthub::V1_0::TransactionResult;
-
-using android::hardware::Return;
-
-using std::chrono::steady_clock;
-
-// If a transaction takes longer than this, we'll allow it to be
-// canceled by a new transaction.  Note we do _not_ automatically
-// cancel a transaction after this much time.  We can have a
-// legal transaction which takes longer than this amount of time,
-// as long as no other new transactions are attempted after this
-// time has expired.
-constexpr auto kMinTransactionCancelTime = std::chrono::seconds(29);
-
-namespace android {
-
-constexpr uint32_t kNanoAppBinaryHeaderVersion = 1;
-
-// Important: this header is explicitly defined as little endian byte order, and
-// therefore may not match host endianness
-struct NanoAppBinaryHeader {
-    uint32_t headerVersion;        // 0x1 for this version
-    uint32_t magic;                // "NANO" (see NANOAPP_MAGIC in context_hub.h)
-    uint64_t appId;                // App Id, contains vendor id
-    uint32_t appVersion;           // Version of the app
-    uint32_t flags;                // Signed, encrypted
-    uint64_t hwHubType;            // Which hub type is this compiled for
-    uint8_t targetChreApiMajorVersion; // Which CHRE API version this is compiled for
-    uint8_t targetChreApiMinorVersion;
-    uint8_t reserved[6];
-} __attribute__((packed));
-
-enum HubMessageType {
-    CONTEXT_HUB_APPS_ENABLE  = 1, // Enables loaded nano-app(s)
-    CONTEXT_HUB_APPS_DISABLE = 2, // Disables loaded nano-app(s)
-    CONTEXT_HUB_LOAD_APP     = 3, // Load a supplied app
-    CONTEXT_HUB_UNLOAD_APP   = 4, // Unload a specified app
-    CONTEXT_HUB_QUERY_APPS   = 5, // Query for app(s) info on hub
-    CONTEXT_HUB_QUERY_MEMORY = 6, // Query for memory info
-    CONTEXT_HUB_OS_REBOOT    = 7, // Request to reboot context HUB OS
-};
-
-constexpr jint OS_APP_ID = -1;
-constexpr jint INVALID_APP_ID = -2;
-
-constexpr jint MIN_APP_ID = 1;
-constexpr jint MAX_APP_ID = 128;
-
-constexpr size_t MSG_HEADER_SIZE = 4;
-constexpr size_t HEADER_FIELD_MSG_TYPE = 0;
-constexpr size_t HEADER_FIELD_MSG_VERSION = 1;
-constexpr size_t HEADER_FIELD_HUB_HANDLE = 2;
-constexpr size_t HEADER_FIELD_APP_INSTANCE = 3;
-
-constexpr size_t HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
-constexpr size_t HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
-constexpr size_t MSG_HEADER_SIZE_LOAD_APP = MSG_HEADER_SIZE + 2;
-
-jint getAppInstanceForAppId(uint64_t app_id);
-int onMessageReceipt(const uint32_t *header,
-                     size_t headerLen,
-                     const char *msg,
-                     size_t msgLen);
-void onHubReset(uint32_t hubId);
-void queryHubForApps(uint32_t hubId);
-void passOnOsResponse(uint32_t hubHandle,
-                      uint32_t msgType,
-                      TransactionResult result,
-                      const int8_t *additionalData,
-                      size_t additionalDataLen);
-
-bool closeLoadTxn(bool success, jint *appInstanceHandle);
-void closeUnloadTxn(bool success);
-int handleQueryAppsResponse(const std::vector<HubAppInfo> apps,
-                               uint32_t hubHandle);
-
-struct JniInfo {
-    JavaVM *vm;
-    jclass contextHubInfoClass;
-    jclass contextHubServiceClass;
-    jclass memoryRegionsClass;
-
-    jobject jContextHubService;
-
-    jmethodID msgReceiptCallBack;
-
-    jmethodID contextHubInfoCtor;
-    jmethodID contextHubInfoSetId;
-    jmethodID contextHubInfoSetName;
-    jmethodID contextHubInfoSetVendor;
-    jmethodID contextHubInfoSetToolchain;
-    jmethodID contextHubInfoSetPlatformVersion;
-    jmethodID contextHubInfoSetStaticSwVersion;
-    jmethodID contextHubInfoSetToolchainVersion;
-    jmethodID contextHubInfoSetPeakMips;
-    jmethodID contextHubInfoSetStoppedPowerDrawMw;
-    jmethodID contextHubInfoSetSleepPowerDrawMw;
-    jmethodID contextHubInfoSetPeakPowerDrawMw;
-    jmethodID contextHubInfoSetSupportedSensors;
-    jmethodID contextHubInfoSetMemoryRegions;
-    jmethodID contextHubInfoSetMaxPacketLenBytes;
-
-    jmethodID contextHubServiceMsgReceiptCallback;
-    jmethodID contextHubServiceAddAppInstance;
-    jmethodID contextHubServiceDeleteAppInstance;
-};
-
-
-
-class TxnManager {
-public:
-    TxnManager() {
-        mData = nullptr;
-        mIsPending = false;
-    }
-
-    ~TxnManager() {
-        closeTxn();
-    }
-
-    int addTxn(HubMessageType txnIdentifier, void *txnData) {
-        std::lock_guard<std::mutex>lock(mLock);
-        if (mIsPending) {
-            ALOGW("Transaction already found pending when trying to add a new one.");
-            return -1;
-        }
-        mIsPending = true;
-        mFirstTimeTxnCanBeCanceled = steady_clock::now() + kMinTransactionCancelTime;
-        mData = txnData;
-        mIdentifier = txnIdentifier;
-
-        return 0;
-    }
-
-    int closeTxn() {
-        std::lock_guard<std::mutex>lock(mLock);
-        closeTxnUnlocked();
-        return 0;
-    }
-
-    bool isTxnPending() {
-        std::lock_guard<std::mutex>lock(mLock);
-        return mIsPending;
-    }
-
-    void closeAnyStaleTxns() {
-        std::lock_guard<std::mutex>lock(mLock);
-        if (mIsPending && steady_clock::now() >= mFirstTimeTxnCanBeCanceled) {
-            ALOGW("Stale transaction canceled");
-            closeTxnUnlocked();
-        }
-    }
-
-    int fetchTxnData(HubMessageType *id, void **data) {
-        if (id == nullptr || data == nullptr) {
-            ALOGW("Null Params isNull{id, data} {%d, %d}",
-                  id == nullptr ? 1 : 0,
-                  data == nullptr ? 1 : 0);
-            return -1;
-        }
-
-        std::lock_guard<std::mutex>lock(mLock);
-        if (!mIsPending) {
-            ALOGW("No Transactions pending");
-            return -1;
-        }
-
-        *id = mIdentifier;
-        *data = mData;
-        return 0;
-    }
-
- private:
-    bool mIsPending;            // Is a transaction pending
-    std::mutex mLock;           // mutex for manager
-    HubMessageType mIdentifier; // What are we doing
-    void *mData;                // Details
-    steady_clock::time_point mFirstTimeTxnCanBeCanceled;
-
-    // Only call this if you hold the lock.
-    void closeTxnUnlocked() {
-        mIsPending = false;
-        free(mData);
-        mData = nullptr;
-    }
-};
-
-
-struct ContextHubServiceCallback : IContexthubCallback {
-    uint32_t mContextHubId;
-
-    ContextHubServiceCallback(uint32_t hubId) {
-        mContextHubId = hubId;
-    }
-
-    virtual Return<void> handleClientMsg(const ContextHubMsg &msg) {
-        jint appHandle = getAppInstanceForAppId(msg.appName);
-        if (appHandle < 0) {
-            ALOGE("Filtering out message due to invalid App Instance.");
-        } else {
-            uint32_t msgHeader[MSG_HEADER_SIZE] = {};
-            msgHeader[HEADER_FIELD_MSG_TYPE] = msg.msgType;
-            msgHeader[HEADER_FIELD_HUB_HANDLE] = mContextHubId;
-            msgHeader[HEADER_FIELD_APP_INSTANCE] = appHandle;
-            onMessageReceipt(msgHeader,
-                             MSG_HEADER_SIZE,
-                             reinterpret_cast<const char *>(msg.msg.data()),
-                             msg.msg.size());
-        }
-
-        return android::hardware::Void();
-    }
-
-    virtual Return<void> handleHubEvent(AsyncEventType evt) {
-        if (evt == AsyncEventType::RESTARTED) {
-            ALOGW("Context Hub handle %d restarted", mContextHubId);
-            onHubReset(mContextHubId);
-        } else {
-            ALOGW("Cannot handle event %u from hub %d", evt, mContextHubId);
-        }
-
-        return android::hardware::Void();
-    }
-
-    virtual Return<void> handleTxnResult(uint32_t txnId,
-                                         TransactionResult result) {
-        ALOGI("Handle transaction result , hubId %" PRIu32 ", txnId %" PRIu32 ", result %" PRIu32,
-              mContextHubId,
-              txnId,
-              result);
-
-        switch(txnId) {
-            case CONTEXT_HUB_APPS_ENABLE:
-            case CONTEXT_HUB_APPS_DISABLE:
-                passOnOsResponse(mContextHubId, txnId, result, nullptr, 0);
-                break;
-
-            case CONTEXT_HUB_UNLOAD_APP:
-                closeUnloadTxn(result == TransactionResult::SUCCESS);
-                passOnOsResponse(mContextHubId, txnId, result, nullptr, 0);
-                break;
-
-            case CONTEXT_HUB_LOAD_APP:
-                {
-                    jint appInstanceHandle = INVALID_APP_ID;
-                    bool appRunningOnHub = (result == TransactionResult::SUCCESS);
-                    if (!(closeLoadTxn(appRunningOnHub, &appInstanceHandle))) {
-                        if (appRunningOnHub) {
-                            // Now we're in an odd situation.  Our nanoapp
-                            // is up and running on the Context Hub.  However,
-                            // something went wrong in our Service code so that
-                            // we're not able to properly track this nanoapp
-                            // in our Service code.  If we tell the Java layer
-                            // things are good, it's a lie because the handle
-                            // we give them will fail when used with the Service.
-                            // If we tell the Java layer this failed, it's kind
-                            // of a lie as well, since this nanoapp is running.
-                            //
-                            // We leave a more robust fix for later, and for
-                            // now just tell the user things have failed.
-                            //
-                            // TODO(b/30835981): Make this situation better.
-                            result = TransactionResult::FAILURE;
-                        }
-                    }
-
-                    passOnOsResponse(mContextHubId,
-                                     txnId,
-                                     result,
-                                     reinterpret_cast<int8_t *>(&appInstanceHandle),
-                                     sizeof(appInstanceHandle));
-                    break;
-                }
-
-            default:
-                ALOGI("unrecognized transction id %" PRIu32, txnId);
-                break;
-        }
-        return android::hardware::Void();
-    }
-
-    virtual Return<void> handleAppsInfo(
-            const android::hardware::hidl_vec<HubAppInfo>& apps) {
-        TransactionResult result = TransactionResult::SUCCESS;
-        handleQueryAppsResponse(apps,mContextHubId);
-        passOnOsResponse(mContextHubId, CONTEXT_HUB_QUERY_APPS, result, nullptr, 0);
-        return android::hardware::Void();
-    }
-
-    virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) {
-        ALOGI("Handle app aport called from %" PRIx64 " with abort code %" PRIu32,
-            appId,
-            abortCode);
-
-        // TODO: Plumb this to the clients interested in this app
-        return android::hardware::Void();
-    }
-
-    void setContextHubId(uint32_t id) {
-        mContextHubId = id;
-    }
-
-    uint32_t getContextHubId() {
-        return(mContextHubId);
-    }
-};
-
-struct AppInstanceInfo {
-    HubAppInfo appInfo;          // returned from the HAL
-    uint64_t truncName;          // Possibly truncated name for logging
-    uint32_t hubHandle;          // Id of the hub this app is on
-    jint instanceId;             // system wide unique instance id - assigned
-};
-
-struct ContextHubInfo {
-    int numHubs;
-    Vector<ContextHub> hubs;
-    sp<IContexthub> contextHub;
-};
-
-struct ContextHubServiceDb {
-    int initialized;
-    ContextHubInfo hubInfo;
-    JniInfo jniInfo;
-    std::queue<jint> freeIds;
-    std::unordered_map<jint, AppInstanceInfo> appInstances;
-    TxnManager txnManager;
-    std::vector<ContextHubServiceCallback *> regCallBacks;
-};
-
-ContextHubServiceDb db;
-
-bool getHubIdForHubHandle(int hubHandle, uint32_t *hubId) {
-    if (hubHandle < 0 || hubHandle >= db.hubInfo.numHubs || hubId == nullptr) {
-        return false;
-    } else {
-        *hubId = db.hubInfo.hubs[hubHandle].hubId;
-        return true;
-    }
-}
-
-int getHubHandleForAppInstance(jint id) {
-    if (!db.appInstances.count(id)) {
-        ALOGD("%s: Cannot find app for app instance %" PRId32,
-              __FUNCTION__,
-              id);
-        return -1;
-    }
-
-    return db.appInstances[id].hubHandle;
-}
-
-jint getAppInstanceForAppId(uint64_t app_id) {
-    auto end = db.appInstances.end();
-    for (auto current = db.appInstances.begin(); current != end; ++current) {
-        if (current->second.appInfo.appId == app_id) {
-            return current->first;
-        }
-    }
-    ALOGD("Cannot find app for app id %" PRIu64 ".", app_id);
-    return -1;
-}
-
-uint64_t getAppIdForAppInstance(jint id) {
-    if (!db.appInstances.count(id)) {
-        return INVALID_APP_ID;
-    }
-    return db.appInstances[id].appInfo.appId;
-}
-
-void queryHubForApps(uint32_t hubId) {
-    Result r = db.hubInfo.contextHub->queryApps(hubId);
-    ALOGD("Sent query for apps to hub %" PRIu32 " with result %" PRIu32, hubId, r);
-}
-
-void sendQueryForApps() {
-    for (int i = 0; i < db.hubInfo.numHubs; i++ ) {
-        queryHubForApps(db.hubInfo.hubs[i].hubId);
-    }
-}
-
-int returnId(jint id) {
-    // Note : This method is not thread safe.
-    // id returned is guaranteed to be in use
-    if (id >= 0) {
-        db.freeIds.push(id);
-        return 0;
-    }
-
-    return -1;
-}
-
-jint generateId() {
-    // Note : This method is not thread safe.
-    jint retVal = -1;
-
-    if (!db.freeIds.empty()) {
-        retVal = db.freeIds.front();
-        db.freeIds.pop();
-    }
-
-    return retVal;
-}
-
-jint addAppInstance(const HubAppInfo *appInfo, uint32_t hubHandle,
-        jint appInstanceHandle, JNIEnv *env) {
-    // Not checking if the apps are indeed distinct
-    AppInstanceInfo entry;
-    assert(appInfo);
-
-
-    entry.appInfo = *appInfo;
-
-    entry.instanceId = appInstanceHandle;
-    entry.truncName = appInfo->appId;
-    entry.hubHandle = hubHandle;
-    db.appInstances[appInstanceHandle] = entry;
-    // Finally - let the service know of this app instance, to populate
-    // the Java cache.
-    env->CallIntMethod(db.jniInfo.jContextHubService,
-                       db.jniInfo.contextHubServiceAddAppInstance,
-                       hubHandle, entry.instanceId,
-                       entry.truncName,
-                       entry.appInfo.version);
-
-    const char *action = (db.appInstances.count(appInstanceHandle) == 0) ? "Added" : "Updated";
-    ALOGI("%s App 0x%" PRIx64 " on hub Handle %" PRId32
-          " as appInstance %" PRId32, action, entry.truncName,
-          entry.hubHandle, appInstanceHandle);
-
-    return appInstanceHandle;
-}
-
-int deleteAppInstance(jint id, JNIEnv *env) {
-    bool fullyDeleted = true;
-
-    if (db.appInstances.count(id)) {
-        db.appInstances.erase(id);
-    } else {
-        ALOGW("Cannot delete App id (%" PRId32 ") from the JNI C++ cache", id);
-        fullyDeleted = false;
-    }
-    returnId(id);
-
-    if ((env == nullptr) ||
-        (env->CallIntMethod(db.jniInfo.jContextHubService,
-                       db.jniInfo.contextHubServiceDeleteAppInstance,
-                       id) != 0)) {
-        ALOGW("Cannot delete App id (%" PRId32 ") from Java cache", id);
-        fullyDeleted = false;
-    }
-
-    if (fullyDeleted) {
-        ALOGI("Deleted App id : %" PRId32, id);
-        return 0;
-    }
-    return -1;
-}
-
-int startLoadAppTxn(uint64_t appId, int hubHandle) {
-    AppInstanceInfo *txnInfo = new AppInstanceInfo();
-    jint instanceId = generateId();
-
-    if (!txnInfo || instanceId < 0) {
-        returnId(instanceId);
-        delete txnInfo;
-        return -1;
-    }
-
-    txnInfo->truncName = appId;
-    txnInfo->hubHandle = hubHandle;
-    txnInfo->instanceId = instanceId;
-
-    txnInfo->appInfo.appId = appId;
-    txnInfo->appInfo.version = -1; // Awaited
-
-    if (db.txnManager.addTxn(CONTEXT_HUB_LOAD_APP, txnInfo) != 0) {
-        returnId(instanceId);
-        delete txnInfo;
-        return -1;
-    }
-
-    return 0;
-}
-
-int startUnloadAppTxn(jint appInstanceHandle) {
-    jint *txnData = new(jint);
-    if (!txnData) {
-        ALOGW("Cannot allocate memory to start unload transaction");
-        return -1;
-    }
-
-    *txnData = appInstanceHandle;
-
-    if (db.txnManager.addTxn(CONTEXT_HUB_UNLOAD_APP, txnData) != 0) {
-        delete txnData;
-        ALOGW("Cannot start transaction to unload app");
-        return -1;
-    }
-
-    return 0;
-}
-
-void getHubsCb(const ::android::hardware::hidl_vec<ContextHub>& hubs)  {
-    for (size_t i = 0; i < hubs.size(); i++) {
-        db.hubInfo.hubs.push_back(hubs[i]);
-    }
-}
-
-void initContextHubService() {
-    db.hubInfo.numHubs = 0;
-
-    db.hubInfo.contextHub = IContexthub::getService();
-
-    if (db.hubInfo.contextHub == nullptr) {
-        ALOGE("Could not load context hub hal");
-    } else {
-        ALOGI("Loaded context hub hal, isRemote %s", db.hubInfo.contextHub->isRemote() ? "TRUE" : "FALSE");
-    }
-
-    // Prep for storing app info
-    for (jint i = MIN_APP_ID; i <= MAX_APP_ID; i++) {
-        db.freeIds.push(i);
-    }
-
-    if (db.hubInfo.contextHub != nullptr) {
-        std::function<void(const ::android::hardware::hidl_vec<ContextHub>& hubs)> f = getHubsCb;
-        if(!db.hubInfo.contextHub->getHubs(f).isOk()) {
-            ALOGW("GetHubs Failed! transport error.");
-            return;
-        };
-
-        int retNumHubs = db.hubInfo.hubs.size();
-        ALOGD("ContextHubModule returned %d hubs ", retNumHubs);
-        db.hubInfo.numHubs = retNumHubs;
-
-        for (int i = 0; i < db.hubInfo.numHubs; i++) {
-            ALOGI("Subscribing to hubHandle %d", i);
-
-            ContextHubServiceCallback *callBackPtr =
-                new ContextHubServiceCallback(db.hubInfo.hubs[i].hubId);
-            db.hubInfo.contextHub->registerCallback(db.hubInfo.hubs[i].hubId,
-                                                    callBackPtr);
-            db.regCallBacks.push_back(callBackPtr);
-        }
-
-        sendQueryForApps();
-
-    } else {
-        ALOGW("No Context Hub Module present");
-    }
-}
-
-void onHubReset(uint32_t hubId) {
-    TransactionResult result = TransactionResult::SUCCESS;
-    db.txnManager.closeTxn();
-    // TODO : Expose this through an api
-    passOnOsResponse(hubId, CONTEXT_HUB_OS_REBOOT, result, nullptr, 0);
-    queryHubForApps(hubId);
-}
-
-int onMessageReceipt(const uint32_t *header,
-                     size_t headerLen,
-                     const char *msg,
-                     size_t msgLen) {
-    JNIEnv *env;
-
-    if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
-      return -1;
-    }
-
-    jbyteArray jmsg = env->NewByteArray(msgLen);
-    if (jmsg == nullptr) {
-        ALOGW("Can't allocate %zu byte array", msgLen);
-        return -1;
-    }
-    jintArray jheader = env->NewIntArray(headerLen);
-    if (jheader == nullptr) {
-        env->DeleteLocalRef(jmsg);
-        ALOGW("Can't allocate %zu int array", headerLen);
-        return -1;
-    }
-
-    env->SetByteArrayRegion(jmsg, 0, msgLen, reinterpret_cast<const jbyte *>(msg));
-    env->SetIntArrayRegion(jheader, 0, headerLen, reinterpret_cast<const jint *>(header));
-
-    int ret = (env->CallIntMethod(db.jniInfo.jContextHubService,
-                                  db.jniInfo.contextHubServiceMsgReceiptCallback,
-                                  jheader,
-                                  jmsg) != 0);
-    env->DeleteLocalRef(jmsg);
-    env->DeleteLocalRef(jheader);
-
-    return ret;
-}
-
-int handleQueryAppsResponse(const std::vector<HubAppInfo> apps,
-                               uint32_t hubHandle) {
-    JNIEnv *env;
-    if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
-            return -1;
-    }
-
-    int numApps = apps.size();
-
-    // We use this information to sync our JNI and Java caches of nanoapp info.
-    // We want to accomplish two things here:
-    // 1) Remove entries from our caches which are stale, and pertained to
-    //    apps no longer running on Context Hub.
-    // 2) Populate our caches with the latest information of all these apps.
-
-    // We make a couple of assumptions here:
-    // A) The JNI and Java caches are in sync with each other (this isn't
-    //    necessarily true; any failure of a single call into Java land to
-    //    update its cache will leave that cache in a bad state.  For NYC,
-    //    we're willing to tolerate this for now).
-    // B) The total number of apps is relatively small, so horribly inefficent
-    //    algorithms aren't too painful.
-    // C) We're going to call this relatively infrequently, so its inefficency
-    //    isn't a big impact.
-
-
-    // (1).  Looking for stale cache entries.  Yes, this is O(N^2).  See
-    // assumption (B).  Per assumption (A), it is sufficient to iterate
-    // over just the JNI cache.
-    auto end = db.appInstances.end();
-    for (auto current = db.appInstances.begin(); current != end; ) {
-        AppInstanceInfo cacheEntry = current->second;
-        // We perform our iteration here because if we call
-        // delete_app_instance() below, it will erase() this entry.
-        current++;
-        bool entryIsStale = true;
-        for (int i = 0; i < numApps; i++) {
-            if (apps[i].appId == cacheEntry.appInfo.appId) {
-                // We found a match; this entry is current.
-                entryIsStale = false;
-                break;
-            }
-        }
-
-        if (entryIsStale) {
-            deleteAppInstance(cacheEntry.instanceId, env);
-        }
-    }
-
-    // (2).  Update our caches with the latest.
-    for (int i = 0; i < numApps; i++) {
-        // We will only have one instance of the app
-        // TODO : Change this logic once we support multiple instances of the same app
-        jint appInstance = getAppInstanceForAppId(apps[i].appId);
-        if (appInstance == -1) {
-            // This is a previously unknown app, let's allocate an "id" for it.
-            appInstance = generateId();
-        }
-        addAppInstance(&apps[i], hubHandle, appInstance, env);
-    }
-    return 0;
-}
-
-// TODO(b/30807327): Do not use raw bytes for additional data.  Use the
-//     JNI interfaces for the appropriate types.
-void passOnOsResponse(uint32_t hubHandle,
-                      uint32_t msgType,
-                      TransactionResult result,
-                      const int8_t *additionalData,
-                      size_t additionalDataLen) {
-    JNIEnv *env;
-
-    if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
-        ALOGW("Cannot latch to JNI env, dropping OS response %" PRIu32,
-              msgType);
-        return;
-    }
-
-    uint32_t header[MSG_HEADER_SIZE];
-    memset(header, 0, sizeof(header));
-
-    if (!additionalData) {
-        additionalDataLen = 0; // clamp
-    }
-    int msgLen = 1 + additionalDataLen;
-
-    int8_t *msg = new int8_t[msgLen];
-
-    if (!msg) {
-        ALOGW("Unexpected : Ran out of memory, cannot send response");
-        return;
-    }
-
-    header[HEADER_FIELD_MSG_TYPE] = msgType;
-    header[HEADER_FIELD_MSG_VERSION] = 0;
-    header[HEADER_FIELD_HUB_HANDLE] = hubHandle;
-    header[HEADER_FIELD_APP_INSTANCE] = OS_APP_ID;
-
-    // Due to API constraints, at the moment we can't change the fact that
-    // we're changing our 4-byte response to a 1-byte value.  But we can prevent
-    // the possible change in sign (and thus meaning) that would happen from
-    // a naive cast.  Further, we can log when we're losing part of the value.
-    // TODO(b/30918279): Don't truncate this result.
-    int8_t truncatedResult;
-    truncatedResult = static_cast<int8_t>(result);
-    msg[0] = truncatedResult;
-
-    if (additionalData) {
-        memcpy(&msg[1], additionalData, additionalDataLen);
-    }
-
-    jbyteArray jmsg = env->NewByteArray(msgLen);
-    jintArray jheader = env->NewIntArray(arraysize(header));
-
-    env->SetByteArrayRegion(jmsg, 0, msgLen, reinterpret_cast<jbyte *>(msg));
-    env->SetIntArrayRegion(jheader, 0, arraysize(header), reinterpret_cast<jint *>(header));
-
-    ALOGI("Passing msg type %" PRIu32 " from app %" PRIu32 " from hub %" PRIu32,
-          header[HEADER_FIELD_MSG_TYPE],
-          header[HEADER_FIELD_APP_INSTANCE],
-          header[HEADER_FIELD_HUB_HANDLE]);
-
-    env->CallIntMethod(db.jniInfo.jContextHubService,
-                       db.jniInfo.contextHubServiceMsgReceiptCallback,
-                       jheader,
-                       jmsg);
-
-    env->DeleteLocalRef(jmsg);
-    env->DeleteLocalRef(jheader);
-
-    delete[] msg;
-}
-
-void closeUnloadTxn(bool success) {
-    void *txnData = nullptr;
-    HubMessageType txnId;
-
-    if (success && db.txnManager.fetchTxnData(&txnId, &txnData) == 0 &&
-        txnId == CONTEXT_HUB_UNLOAD_APP) {
-        JNIEnv *env;
-        if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
-            ALOGW("Could not attach to JVM !");
-            env = nullptr;
-        }
-        jint handle = *reinterpret_cast<jint *>(txnData);
-        deleteAppInstance(handle, env);
-    } else {
-        ALOGW("Could not unload the app successfully ! success %d, txnData %p",
-              success,
-              txnData);
-    }
-
-    db.txnManager.closeTxn();
-}
-
-bool closeLoadTxn(bool success, jint *appInstanceHandle) {
-    void *txnData;
-    HubMessageType txnId;
-
-    if (success && db.txnManager.fetchTxnData(&txnId, &txnData) == 0 &&
-        txnId == CONTEXT_HUB_LOAD_APP) {
-        AppInstanceInfo *info = static_cast<AppInstanceInfo *>(txnData);
-        *appInstanceHandle = info->instanceId;
-
-        JNIEnv *env;
-        if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) == JNI_OK) {
-            addAppInstance(&info->appInfo, info->hubHandle, info->instanceId, env);
-        } else {
-            ALOGW("Could not attach to JVM !");
-            success = false;
-        }
-        // While we just called addAppInstance above, our info->appInfo was
-        // incomplete (for example, the 'version' is hardcoded to -1).  So we
-        // trigger an additional query to the CHRE, so we'll be able to get
-        // all the app "info", and have our JNI and Java caches with the
-        // full information.
-        sendQueryForApps();
-    } else {
-        ALOGW("Could not load the app successfully ! Unexpected failure");
-        *appInstanceHandle = INVALID_APP_ID;
-        success = false;
-    }
-
-    db.txnManager.closeTxn();
-    return success;
-}
-
-int initJni(JNIEnv *env, jobject instance) {
-    if (env->GetJavaVM(&db.jniInfo.vm) != JNI_OK) {
-        return -1;
-    }
-
-    db.jniInfo.jContextHubService = env->NewGlobalRef(instance);
-
-    db.jniInfo.contextHubInfoClass =
-            env->FindClass("android/hardware/location/ContextHubInfo");
-    db.jniInfo.contextHubServiceClass =
-            env->FindClass("com/android/server/location/ContextHubService");
-
-    db.jniInfo.memoryRegionsClass =
-            env->FindClass("android/hardware/location/MemoryRegion");
-
-    db.jniInfo.contextHubInfoCtor =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass, "<init>", "()V");
-    db.jniInfo.contextHubInfoSetId =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass, "setId", "(I)V");
-    db.jniInfo.contextHubInfoSetName =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass, "setName", "(Ljava/lang/String;)V");
-    db.jniInfo.contextHubInfoSetVendor =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setVendor",
-                             "(Ljava/lang/String;)V");
-    db.jniInfo.contextHubInfoSetToolchain =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setToolchain",
-                             "(Ljava/lang/String;)V");
-    db.jniInfo.contextHubInfoSetPlatformVersion =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setPlatformVersion",
-                             "(I)V");
-    db.jniInfo.contextHubInfoSetStaticSwVersion =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setStaticSwVersion",
-                             "(I)V");
-    db.jniInfo.contextHubInfoSetToolchainVersion =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setToolchainVersion",
-                             "(I)V");
-    db.jniInfo.contextHubInfoSetPeakMips =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setPeakMips",
-                             "(F)V");
-    db.jniInfo.contextHubInfoSetStoppedPowerDrawMw =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setStoppedPowerDrawMw",
-                             "(F)V");
-    db.jniInfo.contextHubInfoSetSleepPowerDrawMw =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setSleepPowerDrawMw",
-                             "(F)V");
-    db.jniInfo.contextHubInfoSetPeakPowerDrawMw =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setPeakPowerDrawMw",
-                             "(F)V");
-    db.jniInfo.contextHubInfoSetSupportedSensors =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setSupportedSensors",
-                             "([I)V");
-    db.jniInfo.contextHubInfoSetMemoryRegions =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setMemoryRegions",
-                             "([Landroid/hardware/location/MemoryRegion;)V");
-    db.jniInfo.contextHubInfoSetMaxPacketLenBytes =
-             env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                              "setMaxPacketLenBytes",
-                              "(I)V");
-    db.jniInfo.contextHubServiceMsgReceiptCallback =
-            env->GetMethodID(db.jniInfo.contextHubServiceClass,
-                             "onMessageReceipt",
-                             "([I[B)I");
-    db.jniInfo.contextHubInfoSetName =
-            env->GetMethodID(db.jniInfo.contextHubInfoClass,
-                             "setName",
-                             "(Ljava/lang/String;)V");
-    db.jniInfo.contextHubServiceAddAppInstance =
-                 env->GetMethodID(db.jniInfo.contextHubServiceClass,
-                                  "addAppInstance",
-                                  "(IIJI)I");
-    db.jniInfo.contextHubServiceDeleteAppInstance =
-                 env->GetMethodID(db.jniInfo.contextHubServiceClass,
-                                  "deleteAppInstance",
-                                  "(I)I");
-
-    return 0;
-}
-
-jobject constructJContextHubInfo(JNIEnv *env, const ContextHub &hub) {
-    jstring jstrBuf;
-    jintArray jintBuf;
-    jobjectArray jmemBuf;
-
-    jobject jHub = env->NewObject(db.jniInfo.contextHubInfoClass,
-                                  db.jniInfo.contextHubInfoCtor);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetId, hub.hubId);
-
-    jstrBuf = env->NewStringUTF(hub.name.c_str());
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetName, jstrBuf);
-    env->DeleteLocalRef(jstrBuf);
-
-    jstrBuf = env->NewStringUTF(hub.vendor.c_str());
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetVendor, jstrBuf);
-    env->DeleteLocalRef(jstrBuf);
-
-    jstrBuf = env->NewStringUTF(hub.toolchain.c_str());
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchain, jstrBuf);
-    env->DeleteLocalRef(jstrBuf);
-
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPlatformVersion, hub.platformVersion);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchainVersion, hub.toolchainVersion);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakMips, hub.peakMips);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw,
-                        hub.stoppedPowerDrawMw);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw,
-                        hub.sleepPowerDrawMw);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw,
-                        hub.peakPowerDrawMw);
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMaxPacketLenBytes,
-                        hub.maxSupportedMsgLen);
-
-
-    jintBuf = env->NewIntArray(hub.connectedSensors.size());
-    int *connectedSensors = new int[hub.connectedSensors.size()];
-
-    if (!connectedSensors) {
-      ALOGW("Cannot allocate memory! Unexpected");
-      assert(false);
-    } else {
-      for (unsigned int i = 0; i < hub.connectedSensors.size(); i++) {
-        // TODO :: Populate connected sensors.
-        //connectedSensors[i] = hub.connectedSensors[i].sensorType;
-        connectedSensors[i] = 0;
-      }
-    }
-
-    env->SetIntArrayRegion(jintBuf, 0, hub.connectedSensors.size(),
-                           connectedSensors);
-
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSupportedSensors, jintBuf);
-    env->DeleteLocalRef(jintBuf);
-
-    // We are not getting the memory regions from the CH Hal - change this when it is available
-    jmemBuf = env->NewObjectArray(0, db.jniInfo.memoryRegionsClass, nullptr);
-    // Note the zero size above. We do not need to set any elements
-    env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMemoryRegions, jmemBuf);
-    env->DeleteLocalRef(jmemBuf);
-
-
-    delete[] connectedSensors;
-    return jHub;
-}
-
-jobjectArray nativeInitialize(JNIEnv *env, jobject instance) {
-    jobject hub;
-    jobjectArray retArray;
-
-    if (initJni(env, instance) < 0) {
-        return nullptr;
-    }
-
-    initContextHubService();
-
-    if (db.hubInfo.numHubs > 1) {
-        ALOGW("Clamping the number of hubs to 1");
-        db.hubInfo.numHubs = 1;
-    }
-
-    retArray = env->NewObjectArray(db.hubInfo.numHubs, db.jniInfo.contextHubInfoClass, nullptr);
-
-    for(int i = 0; i < db.hubInfo.numHubs; i++) {
-        hub = constructJContextHubInfo(env, db.hubInfo.hubs[i]);
-        env->SetObjectArrayElement(retArray, i, hub);
-    }
-
-    return retArray;
-}
-
-Result sendLoadNanoAppRequest(uint32_t hubId,
-                              jbyte *data,
-                              size_t dataBufferLength) {
-    auto header = reinterpret_cast<const NanoAppBinaryHeader *>(data);
-    Result result;
-
-    if (dataBufferLength < sizeof(NanoAppBinaryHeader)) {
-        ALOGE("Got short NanoApp, length %zu", dataBufferLength);
-        result = Result::BAD_PARAMS;
-    } else if (header->headerVersion != htole32(kNanoAppBinaryHeaderVersion)) {
-        ALOGE("Got unexpected NanoApp header version %" PRIu32,
-              letoh32(header->headerVersion));
-        result = Result::BAD_PARAMS;
-    } else {
-        NanoAppBinary nanoapp;
-
-        // Data from the common nanoapp header goes into explicit fields
-        nanoapp.appId      = letoh64(header->appId);
-        nanoapp.appVersion = letoh32(header->appVersion);
-        nanoapp.flags      = letoh32(header->flags);
-        nanoapp.targetChreApiMajorVersion = header->targetChreApiMajorVersion;
-        nanoapp.targetChreApiMinorVersion = header->targetChreApiMinorVersion;
-
-        // Everything past the header goes in customBinary
-        auto dataBytes = reinterpret_cast<const uint8_t *>(data);
-        std::vector<uint8_t> customBinary(
-            dataBytes + sizeof(NanoAppBinaryHeader),
-            dataBytes + dataBufferLength);
-        nanoapp.customBinary = std::move(customBinary);
-
-        ALOGW("Calling Load NanoApp on hub %d", hubId);
-        result = db.hubInfo.contextHub->loadNanoApp(hubId,
-                                                    nanoapp,
-                                                    CONTEXT_HUB_LOAD_APP);
-    }
-
-    return result;
-}
-
-jint nativeSendMessage(JNIEnv *env,
-                       jobject instance,
-                       jintArray header_,
-                       jbyteArray data_) {
-    // With the new binderized HAL definition, this function can be made much simpler.
-    // All the magic can be removed. This is not however needed for the default implementation
-    // TODO :: Change the JNI interface to conform to the new HAL interface and clean up this
-    // function
-    jint retVal = -1; // Default to failure
-
-    jint *header = env->GetIntArrayElements(header_, 0);
-    size_t numHeaderElements = env->GetArrayLength(header_);
-    jbyte *data = env->GetByteArrayElements(data_, 0);
-    size_t dataBufferLength = env->GetArrayLength(data_);
-
-    if (numHeaderElements < MSG_HEADER_SIZE) {
-        ALOGW("Malformed header len");
-        return -1;
-    }
-
-    jint appInstanceHandle = header[HEADER_FIELD_APP_INSTANCE];
-    uint32_t msgType = header[HEADER_FIELD_MSG_TYPE];
-    int hubHandle = -1;
-    uint64_t appId;
-
-    if (msgType == CONTEXT_HUB_UNLOAD_APP) {
-        hubHandle = getHubHandleForAppInstance(appInstanceHandle);
-    } else if (msgType == CONTEXT_HUB_LOAD_APP) {
-        if (numHeaderElements < MSG_HEADER_SIZE_LOAD_APP) {
-            return -1;
-        }
-        uint64_t appIdLo = header[HEADER_FIELD_LOAD_APP_ID_LO];
-        uint64_t appIdHi = header[HEADER_FIELD_LOAD_APP_ID_HI];
-        appId = appIdHi << 32 | appIdLo;
-
-        hubHandle = header[HEADER_FIELD_HUB_HANDLE];
-    } else {
-        hubHandle = header[HEADER_FIELD_HUB_HANDLE];
-    }
-
-    uint32_t hubId = -1;
-    if (!getHubIdForHubHandle(hubHandle, &hubId)) {
-        ALOGD("Invalid hub Handle %d", hubHandle);
-        return -1;
-    }
-
-    if (msgType == CONTEXT_HUB_LOAD_APP ||
-        msgType == CONTEXT_HUB_UNLOAD_APP) {
-
-        db.txnManager.closeAnyStaleTxns();
-
-        if (db.txnManager.isTxnPending()) {
-            // TODO : There is a race conditio
-            ALOGW("Cannot load or unload app while a transaction is pending !");
-            return -1;
-        } else if (msgType == CONTEXT_HUB_LOAD_APP) {
-            if (startLoadAppTxn(appId, hubHandle) != 0) {
-                ALOGW("Cannot Start Load Transaction");
-                return -1;
-            }
-        } else if (msgType == CONTEXT_HUB_UNLOAD_APP) {
-            if (startUnloadAppTxn(appInstanceHandle) != 0) {
-                ALOGW("Cannot Start UnLoad Transaction");
-                return -1;
-            }
-        }
-    }
-
-    Result result;
-
-    if (msgType == CONTEXT_HUB_UNLOAD_APP) {
-        ALOGW("Calling UnLoad NanoApp for app %" PRIx64 " on hub %" PRIu32,
-              db.appInstances[appInstanceHandle].appInfo.appId,
-              hubId);
-        result = db.hubInfo.contextHub->unloadNanoApp(
-                hubId, db.appInstances[appInstanceHandle].appInfo.appId, CONTEXT_HUB_UNLOAD_APP);
-    } else {
-        if (appInstanceHandle == OS_APP_ID) {
-            if (msgType == CONTEXT_HUB_LOAD_APP) {
-                result = sendLoadNanoAppRequest(hubId, data, dataBufferLength);
-            } else if (msgType == CONTEXT_HUB_QUERY_APPS) {
-                result = db.hubInfo.contextHub->queryApps(hubId);
-            } else {
-                ALOGD("Dropping OS addresses message of type - %" PRIu32, msgType);
-                result = Result::BAD_PARAMS;
-            }
-        } else {
-            appId = getAppIdForAppInstance(appInstanceHandle);
-            if (appId == static_cast<uint64_t>(INVALID_APP_ID)) {
-                ALOGD("Cannot find application instance %d", appInstanceHandle);
-                result = Result::BAD_PARAMS;
-            } else if (hubHandle != getHubHandleForAppInstance(appInstanceHandle)) {
-                ALOGE("Given hubHandle (%d) doesn't match expected for app instance (%d)",
-                      hubHandle,
-                      getHubHandleForAppInstance(appInstanceHandle));
-                result = Result::BAD_PARAMS;
-            } else {
-                ContextHubMsg msg;
-                msg.appName = appId;
-                msg.msgType = msgType;
-                msg.msg.setToExternal((unsigned char *)data, dataBufferLength);
-
-                ALOGW("Sending msg of type %" PRIu32 " len %zu to app %" PRIx64 " on hub %" PRIu32,
-                       msgType,
-                       dataBufferLength,
-                       appId,
-                       hubId);
-                result = db.hubInfo.contextHub->sendMessageToHub(hubId, msg);
-            }
-        }
-    }
-
-    if (result != Result::OK) {
-        ALOGD("Send Message failure - %d", result);
-        if (msgType == CONTEXT_HUB_LOAD_APP) {
-            jint ignored;
-            closeLoadTxn(false, &ignored);
-        } else if (msgType == CONTEXT_HUB_UNLOAD_APP) {
-            closeUnloadTxn(false);
-        }
-    } else {
-        retVal = 0;
-    }
-
-    env->ReleaseIntArrayElements(header_, header, 0);
-    env->ReleaseByteArrayElements(data_, data, 0);
-
-    return retVal;
-}
-
-//--------------------------------------------------------------------------------------------------
-//
-const JNINativeMethod gContextHubServiceMethods[] = {
-    {"nativeInitialize",
-            "()[Landroid/hardware/location/ContextHubInfo;",
-            reinterpret_cast<void*>(nativeInitialize)},
-    {"nativeSendMessage",
-            "([I[B)I",
-            reinterpret_cast<void*>(nativeSendMessage)}
-};
-
-int register_android_server_location_ContextHubService(JNIEnv *env)
-{
-    RegisterMethodsOrDie(env, "com/android/server/location/ContextHubService",
-            gContextHubServiceMethods, NELEM(gContextHubServiceMethods));
-
-    return 0;
-}
-
-}//namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 2f45181..46d5043 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -39,7 +39,6 @@
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
-int register_android_server_location_ContextHubService(JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
@@ -82,7 +81,6 @@
     register_android_server_vr_VrManagerService(env);
     register_android_server_VibratorService(env);
     register_android_server_SystemServer(env);
-    register_android_server_location_ContextHubService(env);
     register_android_server_location_GnssLocationProvider(env);
     register_android_server_connectivity_Vpn(env);
     register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 74a7bd4a..33f4e34 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
 import android.os.IIncidentManager;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -360,6 +361,9 @@
             // to avoid throwing BadParcelableException.
             BaseBundle.setShouldDefuse(true);
 
+            // Within the system server, when parceling exceptions, include the stack trace
+            Parcel.setStackTraceParceling(true);
+
             // Ensure binder calls into the system always run at foreground priority.
             BinderInternal.disableBackgroundScheduling(true);
 
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 70e8a16..c145e82 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -144,7 +144,9 @@
 
     // Use a Testable subclass so we can simulate calls from the system without failing.
     private static class TestableNotificationManagerService extends NotificationManagerService {
-        public TestableNotificationManagerService(Context context) { super(context); }
+        public TestableNotificationManagerService(Context context) {
+            super(context);
+        }
 
         @Override
         protected boolean isCallingUidSystem() {
@@ -160,6 +162,11 @@
         protected ICompanionDeviceManager getCompanionManager() {
             return null;
         }
+
+        @Override
+        protected void reportSeen(NotificationRecord r) {
+            return;
+        }
     }
 
     @Before
@@ -1865,8 +1872,8 @@
     }
 
     @Test
-    public void testStats_updatedOnExpansion() throws Exception {
-        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+    public void testStats_updatedOnUserExpansion() throws Exception {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
         mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true);
@@ -1876,6 +1883,17 @@
     }
 
     @Test
+    public void testStats_notUpdatedOnAutoExpansion() throws Exception {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true);
+        assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false);
+        assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
+    }
+
+    @Test
     public void testStats_updatedOnViewSettings() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f1e76ab..9a5ebed 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -57,6 +57,7 @@
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 3ac56bb..57da0af 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -30,6 +30,6 @@
     <string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string>
 
     <string name="config_batterySaverDeviceSpecificConfig_1"></string>
-    <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string>
-    <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string>
+    <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string>
+    <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string>
 </resources>
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index f9933fb6..bc503c4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -95,8 +95,7 @@
     public void setUp() throws Exception {
         super.setUp();
         mService = createActivityManagerService();
-        mPackageManager = mock(IPackageManager.class);
-        mStarter = new ActivityStarter(mService, mPackageManager);
+        mStarter = new ActivityStarter(mService);
     }
 
     @Test
@@ -178,7 +177,7 @@
             int expectedResult) {
         final ActivityManagerService service = createActivityManagerService();
         final IPackageManager packageManager = mock(IPackageManager.class);
-        final ActivityStarter starter = new ActivityStarter(service, packageManager);
+        final ActivityStarter starter = new ActivityStarter(service);
 
         final IApplicationThread caller = mock(IApplicationThread.class);
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 9c949ad..9683e22 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -36,6 +36,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -90,6 +91,7 @@
 
     protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
         service = spy(service);
+        doReturn(mock(IPackageManager.class)).when(service).getPackageManager();
         service.mWindowManager = prepareMockWindowManager();
         return service;
     }
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
index f22dfdc..c7fd551 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -11,7 +11,6 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstantAppInfo;
 import android.content.pm.InstrumentationInfo;
@@ -623,12 +622,6 @@
     }
 
     @Override
-    public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
-            String installerPackageName) {
-
-    }
-
-    @Override
     public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
             String installerPackageName) {
 
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index d9fac87..6938e0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -99,10 +99,12 @@
         assertNotNull(mInjector.mSensorListener);
         assertNotNull(mInjector.mSettingsObserver);
         assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
         mTracker.stop();
         assertNull(mInjector.mSensorListener);
         assertNull(mInjector.mSettingsObserver);
         assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
     }
 
     @Test
@@ -399,6 +401,52 @@
     }
 
     @Test
+    public void testWritePrunesOldEvents() throws Exception {
+        final int brightness = 20;
+
+        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+
+        startTracker(mTracker);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                batteryChangeEvent(30, 100));
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
+        final long sensorTime = mInjector.currentTimeMillis();
+        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+                Settings.System.SCREEN_BRIGHTNESS));
+
+        // 31 days later
+        mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
+        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+                Settings.System.SCREEN_BRIGHTNESS));
+        final long eventTime = mInjector.currentTimeMillis();
+
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0).getList();
+        assertEquals(2, events.size());
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mTracker.writeEventsLocked(baos);
+        events = mTracker.getEvents(0).getList();
+        mTracker.stop();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(eventTime, event.timeStamp);
+
+        // We will keep one of the old sensor events because we keep 1 event outside the window.
+        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
+        assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
+        assertEquals(brightness, event.brightness);
+        assertEquals(0.3, event.batteryLevel, 0.01f);
+        assertTrue(event.nightMode);
+        assertEquals(3339, event.colorTemperature);
+    }
+
+    @Test
     public void testParcelUnParcel() {
         Parcel parcel = Parcel.obtain();
         BrightnessChangeEvent event = new BrightnessChangeEvent();
@@ -516,6 +564,7 @@
         long mCurrentTimeMillis = System.currentTimeMillis();
         long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
         Handler mHandler;
+        boolean mIdleScheduled;
 
         public TestInjector(Handler handler) {
             mHandler = handler;
@@ -636,5 +685,14 @@
             focusedStack.topActivity = new ComponentName("a.package", "a.class");
             return focusedStack;
         }
+
+        public void scheduleIdleJob(Context context) {
+            // Don't actually schedule jobs during unit tests.
+            mIdleScheduled = true;
+        }
+
+        public void cancelIdleJob(Context context) {
+            mIdleScheduled = false;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 4db9a30..36d0c8b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -17,12 +17,15 @@
 package com.android.server.pm.dex;
 
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.os.Build;
 import android.os.UserHandle;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.server.pm.Installer;
+
 import dalvik.system.DelegateLastClassLoader;
 import dalvik.system.PathClassLoader;
 import dalvik.system.VMRuntime;
@@ -36,8 +39,13 @@
 import java.util.Map;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -45,6 +53,12 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
@@ -56,6 +70,12 @@
     private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
             DelegateLastClassLoader.class.getName();
 
+    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+    @Mock Installer mInstaller;
+    @Mock IPackageManager mPM;
+    private final Object mInstallLock = new Object();
+    @Mock DexManager.Listener mListener;
+
     private DexManager mDexManager;
 
     private TestData mFooUser0;
@@ -90,7 +110,8 @@
         mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
                 DELEGATE_LAST_CLASS_LOADER_NAME);
 
-        mDexManager = new DexManager(null, null, null, null);
+        mDexManager = new DexManager(
+            mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock, mListener);
 
         // Foo and Bar are available to user0.
         // Only Bar is available to user1;
@@ -440,6 +461,20 @@
 
     }
 
+    @Test
+    public void testReconcileSecondaryDexFiles_invokesListener() throws Exception {
+        List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
+        notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+
+        when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0))
+                .thenReturn(mFooUser0.mPackageInfo);
+
+        mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName());
+
+        verify(mListener, times(fooSecondaries.size()))
+                .onReconcileSecondaryDexFile(any(ApplicationInfo.class),
+                        any(DexUseInfo.class), anyString(), anyInt());
+    }
 
     private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
             List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
@@ -492,12 +527,12 @@
     }
 
     private PackageUseInfo getPackageUseInfo(TestData testData) {
-        assertTrue(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
-        return mDexManager.getPackageUseInfoOrDefault(testData.mPackageInfo.packageName);
+        assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
+        return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
     }
 
     private void assertNoUseInfo(TestData testData) {
-        assertFalse(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
+        assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
     }
 
     private static PackageInfo getMockPackageInfo(String packageName, int userId) {
@@ -555,8 +590,8 @@
 
         List<String> getSecondaryDexPathsFromProtectedDirs() {
             List<String> paths = new ArrayList<>();
-            paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary6.dex");
-            paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary7.dex");
+            paths.add(mPackageInfo.applicationInfo.deviceProtectedDataDir + "/secondary6.dex");
+            paths.add(mPackageInfo.applicationInfo.credentialProtectedDataDir + "/secondary7.dex");
             return paths;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 5b6225e..20cf733 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -18,13 +18,13 @@
 import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.os.Handler;
-import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 
 import com.android.frameworks.servicestests.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -68,6 +68,12 @@
         int getDeviceSpecificConfigResId() {
             return mDeviceSpecificConfigResId;
         }
+
+
+        @VisibleForTesting
+        void onChange() {
+            onChange(true, null);
+        }
     }
 
     @Mock
@@ -221,31 +227,37 @@
         mMockGlobalSettings.put(Global.BATTERY_SAVER_CONSTANTS, "");
         mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, "");
 
-        mBatterySaverPolicy.onChangeForTest();
+        mBatterySaverPolicy.onChange();
         assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
 
 
         mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_2;
 
-        mBatterySaverPolicy.onChangeForTest();
+        mBatterySaverPolicy.onChange();
         assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString())
-                .isEqualTo("{/sys/a=1, /sys/b=2}");
-
+                .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " +
+                "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
 
         mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
 
-        mBatterySaverPolicy.onChangeForTest();
-        assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/c=4}");
-        assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{/sys/a=3}");
+        mBatterySaverPolicy.onChange();
+        assertThat(mBatterySaverPolicy.getFileValues(true).toString())
+                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " +
+                        "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
+        assertThat(mBatterySaverPolicy.getFileValues(false).toString())
+                .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
 
 
         mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
-                "file-on:/proc/z=4");
+                "cpufreq-i=3:1234567890/4:014/5:015");
 
-        mBatterySaverPolicy.onChangeForTest();
-        assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/z=4}");
+        mBatterySaverPolicy.onChange();
+        assertThat(mBatterySaverPolicy.getFileValues(true).toString())
+                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " +
+                        "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " +
+                        "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
new file mode 100644
index 0000000..f72ec34
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.power.batterysaver;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CpuFrequenciesTest {
+    private void check(ArrayMap<String, String> expected, String config) {
+        assertEquals(expected, (new CpuFrequencies().parseString(config))
+                .toSysFileMap());
+    }
+
+    @Test
+    public void test() {
+        check(new ArrayMap<>(), "");
+
+        final ArrayMap<String, String> expected = new ArrayMap<>();
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+        check(expected, "0:0");
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+        expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1");
+        check(expected, "0:0/1:1");
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq", "0");
+        expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1234567890");
+        check(expected, "2:0/1:1234567890");
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "1900800");
+        expected.put("/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq", "1958400");
+        check(expected, "0:1900800/4:1958400");
+
+        check(expected, "0:1900800/4:1958400/"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/1"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/a:1"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/1:"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/1:b"); // Shouldn't crash.
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
new file mode 100644
index 0000000..7e2a7d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.power.batterysaver;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FileUpdaterTest {
+
+    private class FileUpdaterTestable extends FileUpdater {
+        FileUpdaterTestable(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+            super(context, looper, maxRetries, retryIntervalMs);
+        }
+
+        @Override
+        String injectReadFromFileTrimmed(String file) throws IOException {
+            return mInjector.injectReadFromFileTrimmed(file);
+        }
+
+        @Override
+        void injectWriteToFile(String file, String value) throws IOException {
+            mInjector.injectWriteToFile(file, value);
+        }
+
+        @Override
+        void injectWtf(String message, Throwable e) {
+            mInjector.injectWtf(message, e);
+        }
+    }
+
+    private interface Injector {
+        String injectReadFromFileTrimmed(String file) throws IOException;
+        void injectWriteToFile(String file, String value) throws IOException;
+        void injectWtf(String message, Throwable e);
+    }
+
+    private Handler mMainHandler;
+
+    @Mock
+    private Injector mInjector;
+
+    private static final int MAX_RETRIES = 3;
+
+    private FileUpdaterTestable mInstance;
+
+    public static <T> T anyOrNull(Class<T> clazz) {
+        return ArgumentMatchers.argThat(value -> true);
+    }
+
+    public static String anyOrNullString() {
+        return ArgumentMatchers.argThat(value -> true);
+    }
+
+    @Before
+    public void setUp() {
+        mMainHandler = new Handler(Looper.getMainLooper());
+
+        MockitoAnnotations.initMocks(this);
+
+        mInstance = newInstance();
+    }
+
+    private FileUpdaterTestable newInstance() {
+        return new FileUpdaterTestable(
+                InstrumentationRegistry.getContext(),
+                Looper.getMainLooper(),
+                MAX_RETRIES,
+                0 /* retry with no delays*/);
+    }
+
+    private void waitUntilMainHandlerDrain() throws Exception {
+        final CountDownLatch l = new CountDownLatch(1);
+        mMainHandler.post(() -> l.countDown());
+        assertTrue(l.await(5, TimeUnit.SECONDS));
+    }
+
+    private void veriryWtf(int times) {
+        verify(mInjector, times(times)).injectWtf(anyOrNullString(), anyOrNull(Throwable.class));
+    }
+
+    @Test
+    public void testNoWrites() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+        // No WTF should have happened.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testSimpleWrite() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+
+        // No WTF should have happened.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testMultiWrites() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("file3", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+        verify(mInjector, times(1)).injectWriteToFile("file3", "33");
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+        verify(mInjector, times(1)).injectWriteToFile("file3", "333");
+
+        // No WTF should have happened.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testCantReadDefault() throws Exception {
+        doThrow(new IOException("can't read")).when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+        veriryWtf(1);
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+        veriryWtf(1);
+    }
+
+    @Test
+    public void testWriteGiveUp() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+        doThrow(new IOException("can't write")).when(mInjector).injectWriteToFile(
+                eq("fail1"), eq("33"));
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("fail1", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+        verify(mInjector, times(MAX_RETRIES + 1)).injectWriteToFile("fail1", "33");
+
+        // 1 WTF.
+        veriryWtf(1);
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+        verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+        // No further WTF.
+        veriryWtf(1);
+    }
+
+    @Test
+    public void testSuccessWithRetry() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+        final AtomicInteger counter = new AtomicInteger();
+        doAnswer((inv) -> {
+            if (counter.getAndIncrement() <= 1) {
+                throw new IOException();
+            }
+            return null;
+            }).when(mInjector).injectWriteToFile(eq("fail1"), eq("33"));
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("fail1", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+        // Should succeed after 2 retries.
+        verify(mInjector, times(3)).injectWriteToFile("fail1", "33");
+
+        // No WTF.
+        veriryWtf(0);
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+        verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+        // Still no WTF.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testAll() throws Exception {
+        // Run multiple tests on the single target instance.
+
+        reset(mInjector);
+        testSimpleWrite();
+
+        reset(mInjector);
+        testWriteGiveUp();
+
+        reset(mInjector);
+        testMultiWrites();
+
+        reset(mInjector);
+        testSuccessWithRetry();
+
+        reset(mInjector);
+        testMultiWrites();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 8531baf..40edfd2 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -17,6 +17,9 @@
 package com.android.server.usage;
 
 import static android.app.usage.AppStandby.*;
+import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
+import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -67,6 +70,10 @@
     private static final long HOUR_MS = 60 * MINUTE_MS;
     private static final long DAY_MS = 24 * HOUR_MS;
 
+    private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS;
+    private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS;
+    private static final long RARE_THRESHOLD = 48 * HOUR_MS;
+
     private MyInjector mInjector;
 
     static class MyContextWrapper extends ContextWrapper {
@@ -168,6 +175,14 @@
             return packageName != null && packageName.equals(mBoundWidgetPackage);
         }
 
+        @Override
+        String getAppIdleSettings() {
+            return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/"
+                    + WORKING_SET_THRESHOLD + "/"
+                    + FREQUENT_THRESHOLD + "/"
+                    + RARE_THRESHOLD;
+        }
+
         // Internal methods
 
         void setDisplayOn(boolean on) {
@@ -225,12 +240,12 @@
         AppStandbyController controller = setupController();
 
         setChargingState(controller, true);
-        mInjector.mElapsedRealtime = 8 * DAY_MS;
+        mInjector.mElapsedRealtime = RARE_THRESHOLD + 1;
         assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
                 mInjector.mElapsedRealtime, false));
 
         setChargingState(controller, false);
-        mInjector.mElapsedRealtime = 16 * DAY_MS;
+        mInjector.mElapsedRealtime = 2 * RARE_THRESHOLD + 2;
         controller.checkIdleStates(USER_ID);
         assertTrue(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
                 mInjector.mElapsedRealtime, false));
@@ -247,43 +262,49 @@
                         false));
     }
 
-    private void reportEvent(AppStandbyController controller, long elapsedTime) {
+    private void reportEvent(AppStandbyController controller, int eventType,
+            long elapsedTime) {
         // Back to ACTIVE on event
         UsageEvents.Event ev = new UsageEvents.Event();
         ev.mPackage = PACKAGE_1;
-        ev.mEventType = UsageEvents.Event.USER_INTERACTION;
+        ev.mEventType = eventType;
         controller.reportEvent(ev, elapsedTime, USER_ID);
     }
 
+    private int getStandbyBucket(AppStandbyController controller) {
+        return controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+                true);
+    }
+
     @Test
     public void testBuckets() throws Exception {
         AppStandbyController controller = setupController();
 
         assertTimeout(controller, 0, STANDBY_BUCKET_NEVER);
 
-        reportEvent(controller, 0);
+        reportEvent(controller, USER_INTERACTION, 0);
 
         // ACTIVE bucket
-        assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+        assertTimeout(controller, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE);
 
         // WORKING_SET bucket
-        assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+        assertTimeout(controller, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET);
 
         // WORKING_SET bucket
-        assertTimeout(controller, 47 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+        assertTimeout(controller, FREQUENT_THRESHOLD - 1, STANDBY_BUCKET_WORKING_SET);
 
         // FREQUENT bucket
-        assertTimeout(controller, 4 * DAY_MS, STANDBY_BUCKET_FREQUENT);
+        assertTimeout(controller, FREQUENT_THRESHOLD + 1, STANDBY_BUCKET_FREQUENT);
 
         // RARE bucket
-        assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_RARE);
+        assertTimeout(controller, RARE_THRESHOLD + 1, STANDBY_BUCKET_RARE);
 
-        reportEvent(controller, 9 * DAY_MS);
+        reportEvent(controller, USER_INTERACTION, RARE_THRESHOLD + 1);
 
-        assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_ACTIVE);
+        assertTimeout(controller, RARE_THRESHOLD + 1, STANDBY_BUCKET_ACTIVE);
 
         // RARE bucket
-        assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+        assertTimeout(controller, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE);
     }
 
     @Test
@@ -293,23 +314,21 @@
 
         assertTimeout(controller, 0, STANDBY_BUCKET_NEVER);
 
-        reportEvent(controller, 0);
+        reportEvent(controller, USER_INTERACTION, 0);
 
         // ACTIVE bucket
-        assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+        assertTimeout(controller, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE);
 
         // WORKING_SET bucket
-        assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+        assertTimeout(controller, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET);
 
         // RARE bucket, should fail because the screen wasn't ON.
-        mInjector.mElapsedRealtime = 9 * DAY_MS;
+        mInjector.mElapsedRealtime = RARE_THRESHOLD + 1;
         controller.checkIdleStates(USER_ID);
-        assertNotEquals(STANDBY_BUCKET_RARE,
-                controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
-                false));
+        assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(controller));
 
         mInjector.setDisplayOn(true);
-        assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+        assertTimeout(controller, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE);
     }
 
     @Test
@@ -318,8 +337,7 @@
         setChargingState(controller, false);
 
         controller.forceIdleState(PACKAGE_1, USER_ID, true);
-        assertEquals(STANDBY_BUCKET_RARE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
-                true));
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(controller));
         assertTrue(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
 
         controller.forceIdleState(PACKAGE_1, USER_ID, false);
@@ -327,4 +345,20 @@
                 true));
         assertFalse(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
     }
+
+    @Test
+    public void testNotificationEvent() throws Exception {
+        AppStandbyController controller = setupController();
+        setChargingState(controller, false);
+
+        reportEvent(controller, USER_INTERACTION, 0);
+        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(controller));
+        mInjector.mElapsedRealtime = 1;
+        reportEvent(controller, NOTIFICATION_SEEN, mInjector.mElapsedRealtime);
+        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(controller));
+
+        controller.forceIdleState(PACKAGE_1, USER_ID, true);
+        reportEvent(controller, NOTIFICATION_SEEN, mInjector.mElapsedRealtime);
+        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(controller));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 05c4853..2224de5 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -31,6 +31,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Base64;
+import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
@@ -1628,10 +1629,10 @@
         newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
         PackageInfo currentSdkPackage = createPackageInfo("currentTargetSdkPackage",
             true /* enabled */, true /* valid */, true /* installed */);
-        currentSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1+1;
+        currentSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK;
         PackageInfo oldSdkPackage = createPackageInfo("oldTargetSdkPackage",
             true /* enabled */, true /* valid */, true /* installed */);
-        oldSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+        oldSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK - 1;
 
         WebViewProviderInfo newSdkProviderInfo =
                 new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 6060881..b55c79b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -129,6 +129,9 @@
             controller.removeStartingWindow();
             waitUntilHandlersIdle();
             assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent));
+
+            controller.getAppWindowToken(mDisplayContent).getParent().getParent().removeImmediately();
+            mDisplayContent.onPendingTransactionApplied();
         }
     }
 
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 bb88264..d9ab5c8 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -51,71 +51,77 @@
 @RunWith(AndroidJUnit4.class)
 public class AppWindowTokenTests extends WindowTestsBase {
 
+    TaskStack mStack;
+    Task mTask;
+    WindowTestUtils.TestAppWindowToken mToken;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mStack = createTaskStackOnDisplay(mDisplayContent);
+        mTask = createTaskInStack(mStack, 0 /* userId */);
+        mToken = new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+
+        mTask.addChild(mToken, 0);
+    }
+
     @Test
     @Presubmit
     public void testAddWindow_Order() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+        assertEquals(0, mToken.getWindowsCount());
 
-        assertEquals(0, token.getWindowsCount());
-
-        final WindowState win1 = createWindow(null, TYPE_APPLICATION, token, "win1");
-        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, token,
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, mToken, "win1");
+        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mToken,
                 "startingWin");
-        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, token, "baseWin");
-        final WindowState win4 = createWindow(null, TYPE_APPLICATION, token, "win4");
+        final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mToken, "baseWin");
+        final WindowState win4 = createWindow(null, TYPE_APPLICATION, mToken, "win4");
 
         // Should not contain the windows that were added above.
-        assertEquals(4, token.getWindowsCount());
-        assertTrue(token.hasWindow(win1));
-        assertTrue(token.hasWindow(startingWin));
-        assertTrue(token.hasWindow(baseWin));
-        assertTrue(token.hasWindow(win4));
+        assertEquals(4, mToken.getWindowsCount());
+        assertTrue(mToken.hasWindow(win1));
+        assertTrue(mToken.hasWindow(startingWin));
+        assertTrue(mToken.hasWindow(baseWin));
+        assertTrue(mToken.hasWindow(win4));
 
         // The starting window should be on-top of all other windows.
-        assertEquals(startingWin, token.getLastChild());
+        assertEquals(startingWin, mToken.getLastChild());
 
         // The base application window should be below all other windows.
-        assertEquals(baseWin, token.getFirstChild());
-        token.removeImmediately();
+        assertEquals(baseWin, mToken.getFirstChild());
+        mToken.removeImmediately();
     }
 
     @Test
     @Presubmit
     public void testFindMainWindow() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+        assertNull(mToken.findMainWindow());
 
-        assertNull(token.findMainWindow());
-
-        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, token, "window1");
-        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
-        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
-        assertEquals(window1, token.findMainWindow());
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1");
+        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window11");
+        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window12");
+        assertEquals(window1, mToken.findMainWindow());
         window1.mAnimatingExit = true;
-        assertEquals(window1, token.findMainWindow());
-        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, token, "window2");
-        assertEquals(window2, token.findMainWindow());
-        token.removeImmediately();
+        assertEquals(window1, mToken.findMainWindow());
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mToken, "window2");
+        assertEquals(window2, mToken.findMainWindow());
+        mToken.removeImmediately();
     }
 
     @Test
     @Presubmit
     public void testGetTopFullscreenWindow() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
+        assertNull(mToken.getTopFullscreenWindow());
 
-        assertNull(token.getTopFullscreenWindow());
-
-        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, token, "window1");
-        final WindowState window11 = createWindow(null, TYPE_APPLICATION, token, "window11");
-        final WindowState window12 = createWindow(null, TYPE_APPLICATION, token, "window12");
-        assertEquals(window12, token.getTopFullscreenWindow());
+        final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1");
+        final WindowState window11 = createWindow(null, TYPE_APPLICATION, mToken, "window11");
+        final WindowState window12 = createWindow(null, TYPE_APPLICATION, mToken, "window12");
+        assertEquals(window12, mToken.getTopFullscreenWindow());
         window12.mAttrs.width = 500;
-        assertEquals(window11, token.getTopFullscreenWindow());
+        assertEquals(window11, mToken.getTopFullscreenWindow());
         window11.mAttrs.width = 500;
-        assertEquals(window1, token.getTopFullscreenWindow());
-        token.removeImmediately();
+        assertEquals(window1, mToken.getTopFullscreenWindow());
+        mToken.removeImmediately();
     }
 
     @Test
@@ -124,27 +130,21 @@
         sWm.mDisplayReady = true;
         sWm.mDisplayEnabled = true;
 
-        // Create an app window with token on a display.
-        final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
-        final Task task = createTaskInStack(stack, 0 /* userId */);
-        final WindowTestUtils.TestAppWindowToken appWindowToken =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
-        task.addChild(appWindowToken, 0);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, appWindowToken);
-        appWindowToken.addWindow(appWindow);
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+        mToken.addWindow(appWindow);
 
         // Set initial orientation and update.
-        appWindowToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null,
                 mDisplayContent.getDisplayId());
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
         appWindow.resizeReported = false;
 
         // Update the orientation to perform 180 degree rotation and check that resize was reported.
-        appWindowToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+        mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
         sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null,
                 mDisplayContent.getDisplayId());
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
@@ -159,18 +159,11 @@
         sWm.mDisplayReady = true;
         sWm.mDisplayEnabled = true;
 
-        // Create an app window with token on a display.
-        final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
-        final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
-        final Task task = createTaskInStack(stack, 0 /* userId */);
-        final WindowTestUtils.TestAppWindowToken appWindowToken =
-                new WindowTestUtils.TestAppWindowToken(defaultDisplayContent);
-        task.addChild(appWindowToken, 0);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, appWindowToken);
-        appWindowToken.addWindow(appWindow);
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
+        mToken.addWindow(appWindow);
 
         // Set initial orientation and update.
         performRotation(Surface.ROTATION_90);
@@ -193,53 +186,49 @@
     @Test
     @Presubmit
     public void testGetOrientation() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
-        token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
-        token.setFillsParent(false);
+        mToken.setFillsParent(false);
         // Can specify orientation if app doesn't fill parent.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
 
-        token.setFillsParent(true);
-        token.hidden = true;
-        token.sendingToBottom = true;
+        mToken.setFillsParent(true);
+        mToken.hidden = true;
+        mToken.sendingToBottom = true;
         // Can not specify orientation if app isn't visible even though it fills parent.
-        assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
         // Can specify orientation if the current orientation candidate is orientation behind.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation(SCREEN_ORIENTATION_BEHIND));
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation(SCREEN_ORIENTATION_BEHIND));
 
-        token.sendingToBottom = false;
-        token.setIsOnTop(true);
-        // Allow for token to provide orientation hidden if on top and not being sent to bottom.
-        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
+        mToken.sendingToBottom = false;
+        mToken.setIsOnTop(true);
+        // Allow for mToken to provide orientation hidden if on top and not being sent to bottom.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
     }
 
     @Test
     @Presubmit
     public void testKeyguardFlagsDuringRelaunch() throws Exception {
-        final WindowTestUtils.TestAppWindowToken token =
-                new WindowTestUtils.TestAppWindowToken(mDisplayContent);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, token);
+        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken);
 
         // Add window with show when locked flag
-        token.addWindow(appWindow);
-        assertTrue(token.containsShowWhenLockedWindow() && token.containsDismissKeyguardWindow());
+        mToken.addWindow(appWindow);
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
 
         // Start relaunching
-        token.startRelaunching();
-        assertTrue(token.containsShowWhenLockedWindow() && token.containsDismissKeyguardWindow());
+        mToken.startRelaunching();
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
 
         // Remove window and make sure that we still report back flag
-        token.removeChild(appWindow);
-        assertTrue(token.containsShowWhenLockedWindow() && token.containsDismissKeyguardWindow());
+        mToken.removeChild(appWindow);
+        assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow());
 
         // Finish relaunching and ensure flag is now not reported
-        token.finishRelaunching();
-        assertFalse(token.containsShowWhenLockedWindow() || token.containsDismissKeyguardWindow());
+        mToken.finishRelaunching();
+        assertFalse(mToken.containsShowWhenLockedWindow() || mToken.containsDismissKeyguardWindow());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
deleted file mode 100644
index c3a471a..0000000
--- a/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
+++ /dev/null
@@ -1,65 +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
- */
-
-package com.android.server.wm;
-
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.hardware.display.DisplayManagerGlobal;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link DimLayerController} class.
- *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.DimLayerControllerTests
- */
-@SmallTest
-@Presubmit
-@org.junit.runner.RunWith(AndroidJUnit4.class)
-public class DimLayerControllerTests extends WindowTestsBase {
-
-    /**
-     * This tests if shared fullscreen dim layer is added when stack is added to display
-     * and is removed when the only stack on the display is removed.
-     */
-    @Test
-    public void testSharedFullScreenDimLayer() throws Exception {
-        // Create a display.
-        final DisplayContent dc = createNewDisplay();
-        assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
-
-        // Add stack with activity.
-        final TaskStack stack = createTaskStackOnDisplay(dc);
-        assertTrue(dc.mDimLayerController.hasDimLayerUser(stack));
-        assertTrue(dc.mDimLayerController.hasSharedFullScreenDimLayer());
-
-        // Remove the only stack on the display and check if the shared dim layer clears.
-        stack.removeImmediately();
-        assertFalse(dc.mDimLayerController.hasDimLayerUser(stack));
-        assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
new file mode 100644
index 0000000..f069d49
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java
@@ -0,0 +1,207 @@
+/*
+ * 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 java.util.HashMap;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+
+/**
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.DimmerTests;
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DimmerTests extends WindowTestsBase {
+    private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
+        final SurfaceControl mControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+
+        @Override
+        SurfaceControl getSurfaceControl() {
+            return mControl;
+        }
+
+        @Override
+        SurfaceControl.Transaction getPendingTransaction() {
+            return mTransaction;
+        }
+    }
+
+    private class MockSurfaceBuildingContainer extends WindowContainer<TestWindowContainer> {
+        final SurfaceSession mSession = new SurfaceSession();
+        SurfaceControl mBuiltSurface = null;
+        final SurfaceControl mHostControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class);
+
+        class MockSurfaceBuilder extends SurfaceControl.Builder {
+            MockSurfaceBuilder(SurfaceSession ss) {
+                super(ss);
+            }
+
+            @Override
+            public SurfaceControl build() {
+                SurfaceControl sc = mock(SurfaceControl.class);
+                mBuiltSurface = sc;
+                return sc;
+            }
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceBuilder(mSession);
+        }
+
+        @Override
+        SurfaceControl getSurfaceControl() {
+            return mHostControl;
+        }
+
+        @Override
+        SurfaceControl.Transaction getPendingTransaction() {
+            return mHostTransaction;
+        }
+    }
+
+    MockSurfaceBuildingContainer mHost;
+    Dimmer mDimmer;
+    SurfaceControl.Transaction mTransaction;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHost = new MockSurfaceBuildingContainer();
+        mTransaction = mock(SurfaceControl.Transaction.class);
+        mDimmer = new Dimmer(mHost);
+    }
+
+    @Test
+    public void testDimAboveNoChildCreatesSurface() throws Exception {
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, alpha);
+        assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface);
+
+        verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha);
+        verify(mTransaction).show(mHost.mBuiltSurface);
+        verify(mTransaction).setLayer(mHost.mBuiltSurface, Integer.MAX_VALUE);
+    }
+
+    @Test
+    public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception {
+        float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, alpha);
+        final SurfaceControl firstSurface = mHost.mBuiltSurface;
+
+        alpha = 0.9f;
+        mDimmer.dimAbove(mTransaction, alpha);
+
+        assertEquals(firstSurface, mHost.mBuiltSurface);
+        verify(mTransaction).setAlpha(firstSurface, 0.9f);
+    }
+
+    @Test
+    public void testUpdateDimsAppliesSize() throws Exception {
+        mDimmer.dimAbove(mTransaction, 0.8f);
+
+        int width = 100;
+        int height = 300;
+        Rect bounds = new Rect(0, 0, width, height);
+        mDimmer.updateDims(mTransaction, bounds);
+        verify(mTransaction).setSize(mHost.mBuiltSurface, width, height);
+    }
+
+    @Test
+    public void testDimAboveNoChildNotReset() throws Exception {
+        mDimmer.dimAbove(mTransaction, 0.8f);
+        mDimmer.resetDimStates();
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mHost.mBuiltSurface, never()).destroy();
+    }
+
+    @Test
+    public void testDimAboveWithChildCreatesSurfaceAboveChild() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface);
+
+        verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha);
+        verify(mTransaction).show(mHost.mBuiltSurface);
+        verify(mTransaction).setRelativeLayer(mHost.mBuiltSurface, child.mControl, 1);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimBelow(mTransaction, child, alpha);
+        assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface);
+
+        verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha);
+        verify(mTransaction).show(mHost.mBuiltSurface);
+        verify(mTransaction).setRelativeLayer(mHost.mBuiltSurface, child.mControl, -1);
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceDestroyedWhenReset() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mHost.mBuiltSurface).destroy();
+    }
+
+    @Test
+    public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() throws Exception {
+        TestWindowContainer child = new TestWindowContainer();
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
+        mDimmer.resetDimStates();
+        mDimmer.dimAbove(mTransaction, child, alpha);
+
+        mDimmer.updateDims(mTransaction, new Rect());
+        verify(mHost.mBuiltSurface, never()).destroy();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
index 887def7..873a01b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -66,7 +66,7 @@
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
 
         mPositioner = new TaskPositioner(sWm);
-        mPositioner.register(display);
+        mPositioner.register(mDisplayContent);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
index b846fd0..0ef78f4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -105,14 +105,10 @@
         final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
         assertEquals(stack, task.mStack);
-        assertTrue(mDisplayContent.mDimLayerController.hasDimLayerUser(stack));
-        assertTrue(mDisplayContent.mDimLayerController.hasDimLayerUser(task));
 
         // Remove stack and check if its child is also removed.
         stack.removeImmediately();
         assertNull(stack.getDisplayContent());
         assertNull(task.mStack);
-        assertFalse(mDisplayContent.mDimLayerController.hasDimLayerUser(stack));
-        assertFalse(mDisplayContent.mDimLayerController.hasDimLayerUser(task));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 53d0bfb..a45695f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -568,11 +568,6 @@
     }
 
     @Override
-    public boolean canMagnifyWindow(int windowType) {
-        return false;
-    }
-
-    @Override
     public boolean isTopLevelWindow(int windowType) {
         return false;
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
deleted file mode 100644
index 3c3514f..0000000
--- a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.platform.test.annotations.Presubmit;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-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.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-
-/**
- * Tests for the {@link WindowLayersController} class.
- *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.WindowLayersControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class WindowLayersControllerTests extends WindowTestsBase {
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception {
-        sWm.mInputMethodTarget = null;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // The Ime has an higher base layer than app windows and lower base layer than system
-        // windows, so it should be above app windows and below system windows if there isn't an IME
-        // target.
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception {
-        final WindowState imeAppTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
-        sWm.mInputMethodTarget = imeAppTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // Ime should be above all app windows and below system windows if it is targeting an app
-        // window.
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTarget);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
-        final WindowState imeAppTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
-        final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
-                TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
-                "imeAppTargetChildAboveWindow");
-        final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
-                TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
-                "imeAppTargetChildBelowWindow");
-
-        sWm.mInputMethodTarget = imeAppTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // Ime should be above all app windows except for child windows that are z-ordered above it
-        // and below system windows if it is targeting an app window.
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTarget);
-        assertWindowLayerGreaterThan(imeAppTargetChildAboveWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTargetChildBelowWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
-        final WindowState appBelowImeTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appBelowImeTarget");
-        final WindowState imeAppTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
-        final WindowState appAboveImeTarget =
-                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appAboveImeTarget");
-
-        sWm.mInputMethodTarget = imeAppTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // Ime should be above all app windows except for non-fullscreen app window above it and
-        // below system windows if it is targeting an app window.
-        assertWindowLayerGreaterThan(mImeWindow, imeAppTarget);
-        assertWindowLayerGreaterThan(mImeWindow, appBelowImeTarget);
-        assertWindowLayerGreaterThan(appAboveImeTarget, mImeWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-        assertWindowLayerGreaterThan(mStatusBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception {
-        final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY,
-                mDisplayContent, "imeSystemOverlayTarget",
-                true /* ownerCanAddInternalSystemWindow */);
-
-        sWm.mInputMethodTarget = imeSystemOverlayTarget;
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        // The IME target base layer is higher than all window except for the nav bar window, so the
-        // IME should be above all windows except for the nav bar.
-        assertWindowLayerGreaterThan(mImeWindow, imeSystemOverlayTarget);
-        assertWindowLayerGreaterThan(mImeWindow, mChildAppWindowAbove);
-        assertWindowLayerGreaterThan(mImeWindow, mAppWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mDockedDividerWindow);
-        assertWindowLayerGreaterThan(mImeWindow, mStatusBarWindow);
-        assertWindowLayerGreaterThan(mNavBarWindow, mImeWindow);
-
-        // And, IME dialogs should always have an higher layer than the IME.
-        assertWindowLayerGreaterThan(mImeDialogWindow, mImeWindow);
-    }
-
-    @Test
-    public void testStackLayers() throws Exception {
-        WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
-                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow");
-        WindowState dockedStackWindow = createWindowOnStack(null,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
-                mDisplayContent, "dockedStackWindow");
-        WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
-                mDisplayContent, "assistantStackWindow");
-
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        assertWindowLayerGreaterThan(dockedStackWindow, mAppWindow);
-        assertWindowLayerGreaterThan(assistantStackWindow, dockedStackWindow);
-        assertWindowLayerGreaterThan(pinnedStackWindow, assistantStackWindow);
-    }
-
-    private void assertWindowLayerGreaterThan(WindowState first, WindowState second)
-            throws Exception {
-        assertGreaterThan(first.mWinAnimator.mAnimLayer, second.mWinAnimator.mAnimLayer);
-    }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 0980f7e..4c5e291 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -73,7 +73,6 @@
     private static boolean sOneTimeSetupDone = false;
     DisplayContent mDisplayContent;
     DisplayInfo mDisplayInfo = new DisplayInfo();
-    WindowLayersController mLayersController;
     WindowState mWallpaperWindow;
     WindowState mImeWindow;
     WindowState mImeDialogWindow;
@@ -98,8 +97,9 @@
 
         final Context context = InstrumentationRegistry.getTargetContext();
         AttributeCache.init(context);
+
         sWm = TestWindowManagerPolicy.getWindowManagerService(context);
-        mLayersController = new WindowLayersController(sWm);
+        beforeCreateDisplay();
 
         context.getDisplay().getDisplayInfo(mDisplayInfo);
         mDisplayContent = createNewDisplay();
@@ -126,6 +126,10 @@
         waitUntilHandlersIdle();
     }
 
+    void beforeCreateDisplay() {
+        // Called before display is created.
+    }
+
     @After
     public void tearDown() throws Exception {
         final LinkedList<WindowState> nonCommonWindows = new LinkedList();
@@ -149,6 +153,14 @@
         waitUntilHandlersIdle();
     }
 
+    /**
+     * @return A SurfaceBuilderFactory to inject in to the WindowManagerService during
+     *         set-up (or null).
+     */
+    SurfaceBuilderFactory getSurfaceBuilderFactory() {
+        return null;
+    }
+
     private WindowState createCommonWindow(WindowState parent, int type, String name) {
         final WindowState win = createWindow(parent, type, name);
         mCommonWindows.add(win);
@@ -162,6 +174,11 @@
         Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second);
     }
 
+    /** Asserts that the first entry is greater than the second entry. */
+    void assertLessThan(int first, int second) throws Exception {
+        Assert.assertTrue("Excepted " + first + " to be less than " + second, first < second);
+    }
+
     /**
      * Waits until the main handler for WM has processed all messages.
      */
@@ -264,7 +281,7 @@
         final int displayId = sNextDisplayId++;
         final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
                 mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
-        return new DisplayContent(display, sWm, mLayersController, new WallpaperController(sWm));
+        return new DisplayContent(display, sWm, new WallpaperController(sWm));
     }
 
     /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
index 692e08b..7219104 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -94,43 +94,6 @@
         assertEquals(null, dc.getWindowToken(token.token));
     }
 
-    @Test
-    public void testAdjustAnimLayer() throws Exception {
-        final WindowTestUtils.TestWindowToken token =
-                new WindowTestUtils.TestWindowToken(0, mDisplayContent);
-        final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
-        final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
-        final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
-        final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
-        final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3");
-
-        window2.mLayer = 100;
-        window3.mLayer = 200;
-
-        // We assign layers once, to get the base values computed by
-        // the controller.
-        mLayersController.assignWindowLayers(mDisplayContent);
-
-        final int window1StartLayer = window1.mWinAnimator.mAnimLayer;
-        final int window11StartLayer = window11.mWinAnimator.mAnimLayer;
-        final int window12StartLayer = window12.mWinAnimator.mAnimLayer;
-        final int window2StartLayer = window2.mWinAnimator.mAnimLayer;
-        final int window3StartLayer = window3.mWinAnimator.mAnimLayer;
-
-        // Then we set an adjustment, and assign them again, they should
-        // be offset.
-        int adj = token.adj = 50;
-        mLayersController.assignWindowLayers(mDisplayContent);
-        final int highestLayer = token.getHighestAnimLayer();
-
-        assertEquals(window1StartLayer + adj, window1.mWinAnimator.mAnimLayer);
-        assertEquals(window11StartLayer + adj, window11.mWinAnimator.mAnimLayer);
-        assertEquals(window12StartLayer + adj, window12.mWinAnimator.mAnimLayer);
-        assertEquals(window2StartLayer + adj, window2.mWinAnimator.mAnimLayer);
-        assertEquals(window3StartLayer + adj, window3.mWinAnimator.mAnimLayer);
-        assertEquals(window3StartLayer + adj, highestLayer);
-    }
-
     /**
      * Test that a window token isn't orphaned by the system when it is requested to be removed.
      * Tokens should only be removed from the system when all their windows are gone.
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
new file mode 100644
index 0000000..f7c4b1f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.util.Log;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+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.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+/**
+ * Tests for the {@link WindowLayersController} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.ZOrderingTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ZOrderingTests extends WindowTestsBase {
+
+    private class LayerRecordingTransaction extends SurfaceControl.Transaction {
+        HashMap<SurfaceControl, Integer> mLayersForControl = new HashMap();
+        HashMap<SurfaceControl, SurfaceControl> mRelativeLayersForControl = new HashMap();
+
+        @Override
+        public SurfaceControl.Transaction setLayer(SurfaceControl sc, int layer) {
+            mRelativeLayersForControl.remove(sc);
+            mLayersForControl.put(sc, layer);
+            return super.setLayer(sc, layer);
+        }
+
+        @Override
+        public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc,
+                SurfaceControl relativeTo,
+                int layer) {
+            mRelativeLayersForControl.put(sc, relativeTo);
+            mLayersForControl.put(sc, layer);
+            return super.setRelativeLayer(sc, relativeTo, layer);
+        }
+
+        int getLayer(SurfaceControl sc) {
+            return mLayersForControl.get(sc);
+        }
+
+        SurfaceControl getRelativeLayer(SurfaceControl sc) {
+            return mRelativeLayersForControl.get(sc);
+        }
+    };
+
+    // We have WM use our Hierarchy recording subclass of SurfaceControl.Builder
+    // such that we can keep track of the parents of Surfaces as they are constructed.
+    private HashMap<SurfaceControl, SurfaceControl> mParentFor = new HashMap();
+
+    private class HierarchyRecorder extends SurfaceControl.Builder {
+        SurfaceControl mPendingParent;
+
+        HierarchyRecorder(SurfaceSession s) {
+            super(s);
+        }
+
+        public SurfaceControl.Builder setParent(SurfaceControl sc) {
+            mPendingParent = sc;
+            return super.setParent(sc);
+        }
+        public SurfaceControl build() {
+            SurfaceControl sc = super.build();
+            mParentFor.put(sc, mPendingParent);
+            mPendingParent = null;
+            return sc;
+        }
+    };
+
+    class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory {
+        public SurfaceControl.Builder make(SurfaceSession s) {
+            return new HierarchyRecorder(s);
+        }
+    };
+
+    private LayerRecordingTransaction mTransaction;
+
+    @Override
+    void beforeCreateDisplay() {
+        // We can't use @Before here because it may happen after WindowTestsBase @Before
+        // which is after construction of the DisplayContent, meaning the HierarchyRecorder
+        // would miss construction of the top-level layers.
+        mTransaction = new LayerRecordingTransaction();
+        sWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory();
+    }
+
+    @After
+    public void after() {
+        mTransaction.close();
+        mParentFor.clear();
+    }
+
+    LinkedList<SurfaceControl> getAncestors(LayerRecordingTransaction t, SurfaceControl sc) {
+        LinkedList<SurfaceControl> p = new LinkedList();
+        SurfaceControl current = sc;
+        do {
+            p.addLast(current);
+
+            SurfaceControl rs = t.getRelativeLayer(current);
+            if (rs != null) {
+                current = rs;
+            } else {
+                current = mParentFor.get(current);
+            }
+        } while (current != null);
+        return p;
+    }
+
+    void assertZOrderGreaterThan(LayerRecordingTransaction t,
+            SurfaceControl left, SurfaceControl right) throws Exception {
+        final LinkedList<SurfaceControl> leftParentChain = getAncestors(t, left);
+        final LinkedList<SurfaceControl> rightParentChain = getAncestors(t, right);
+
+        SurfaceControl commonAncestor = null;
+        SurfaceControl leftTop = leftParentChain.peekLast();
+        SurfaceControl rightTop = rightParentChain.peekLast();
+        while (leftTop != null && rightTop != null && leftTop == rightTop) {
+            commonAncestor = leftParentChain.removeLast();
+            rightParentChain.removeLast();
+            leftTop = leftParentChain.peekLast();
+            rightTop = rightParentChain.peekLast();
+        }
+
+        if (rightTop == null) { // right is the parent of left.
+            assertGreaterThan(t.getLayer(leftTop), 0);
+        } else if (leftTop == null) { // left is the parent of right.
+            assertGreaterThan(0, t.getLayer(rightTop));
+        } else {
+            assertGreaterThan(t.getLayer(leftTop),
+                    t.getLayer(rightTop));
+        }
+    }
+
+    void assertWindowLayerGreaterThan(LayerRecordingTransaction t,
+            WindowState left, WindowState right) throws Exception {
+        assertZOrderGreaterThan(t, left.getSurfaceControl(), right.getSurfaceControl());
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception {
+        sWm.mInputMethodTarget = null;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // The Ime has an higher base layer than app windows and lower base layer than system
+        // windows, so it should be above app windows and below system windows if there isn't an IME
+        // target.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception {
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows and below system windows if it is targeting an app
+        // window.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+        final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
+                TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
+                "imeAppTargetChildAboveWindow");
+        final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
+                TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
+                "imeAppTargetChildBelowWindow");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows except for child windows that are z-ordered above it
+        // and below system windows if it is targeting an app window.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+        assertWindowLayerGreaterThan(mTransaction, imeAppTargetChildAboveWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
+        final WindowState appBelowImeTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appBelowImeTarget");
+        final WindowState imeAppTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
+        final WindowState appAboveImeTarget =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appAboveImeTarget");
+
+        sWm.mInputMethodTarget = imeAppTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // Ime should be above all app windows except for non-fullscreen app window above it and
+        // below system windows if it is targeting an app window.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeAppTarget);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, appBelowImeTarget);
+        assertWindowLayerGreaterThan(mTransaction, appAboveImeTarget, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception {
+        final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY,
+                mDisplayContent, "imeSystemOverlayTarget",
+                true /* ownerCanAddInternalSystemWindow */);
+
+        sWm.mInputMethodTarget = imeSystemOverlayTarget;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        // The IME target base layer is higher than all window except for the nav bar window, so the
+        // IME should be above all windows except for the nav bar.
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, imeSystemOverlayTarget);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+
+        // The IME has a higher base layer than the status bar so we may expect it to go
+        // above the status bar once they are both in the Non-App layer, as past versions of this
+        // test enforced. However this seems like the wrong behavior unless the status bar is the
+        // IME target.
+        assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow);
+        assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testAssignWindowLayers_ForStatusBarImeTarget() throws Exception {
+        sWm.mInputMethodTarget = mStatusBarWindow;
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow);
+        assertWindowLayerGreaterThan(mTransaction, mImeWindow, mStatusBarWindow);
+
+        // And, IME dialogs should always have an higher layer than the IME.
+        assertWindowLayerGreaterThan(mTransaction, mImeDialogWindow, mImeWindow);
+    }
+
+    @Test
+    public void testStackLayers() throws Exception {
+        final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
+                "pinnedStackWindow");
+        final WindowState dockedStackWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "dockedStackWindow");
+        final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
+                mDisplayContent, "assistantStackWindow");
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowLayerGreaterThan(mTransaction, dockedStackWindow, mAppWindow);
+        assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow);
+        assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow);
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index c5ca330..7ca17af 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -91,8 +91,6 @@
     private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
     private long mScreenOnDuration; // Total screen on duration since device was "born"
 
-    private long mElapsedTimeThreshold;
-    private long mScreenOnTimeThreshold;
     private final File mStorageDir;
 
     private boolean mScreenOn;
@@ -113,11 +111,6 @@
         readScreenOnTime();
     }
 
-    public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
-        mElapsedTimeThreshold = elapsedTimeThreshold;
-        mScreenOnTimeThreshold = screenOnTimeThreshold;
-    }
-
     public void updateDisplay(boolean screenOn, long elapsedRealtime) {
         if (screenOn == mScreenOn) return;
 
@@ -186,7 +179,7 @@
         writeScreenOnTime();
     }
 
-    public void reportUsage(String packageName, int userId, long elapsedRealtime) {
+    public int reportUsage(String packageName, int userId, long elapsedRealtime) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
@@ -197,12 +190,33 @@
                 + (elapsedRealtime - mElapsedSnapshot);
         appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
         appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
-        appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
-        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
-        if (DEBUG) {
-            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
-                    + ", reason=" + appUsageHistory.bucketingReason);
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+            if (DEBUG) {
+                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                        + ", reason=" + appUsageHistory.bucketingReason);
+            }
         }
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+        return appUsageHistory.currentBucket;
+    }
+
+    public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
+            if (DEBUG) {
+                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                        + ", reason=" + appUsageHistory.bucketingReason);
+            }
+        }
+        // TODO: Should this be a different reason for partial usage?
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+        return appUsageHistory.currentBucket;
     }
 
     public void setIdle(String packageName, int userId, long elapsedRealtime) {
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 5623a68..cd0fce6 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -91,14 +91,14 @@
             0,
             0,
             COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
-            COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+            COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR
     };
 
     static final long[] ELAPSED_TIME_THRESHOLDS = {
             0,
             COMPRESS_TIME ?  1 * ONE_MINUTE : 12 * ONE_HOUR,
-            COMPRESS_TIME ?  4 * ONE_MINUTE :  2 * ONE_DAY,
-            COMPRESS_TIME ? 16 * ONE_MINUTE :  8 * ONE_DAY
+            COMPRESS_TIME ?  4 * ONE_MINUTE : 24 * ONE_HOUR,
+            COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR
     };
 
     static final int[] THRESHOLD_BUCKETS = {
@@ -140,9 +140,7 @@
     static final int MSG_PAROLE_STATE_CHANGED = 9;
     static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
 
-    long mAppIdleScreenThresholdMillis;
     long mCheckIdleIntervalMillis;
-    long mAppIdleWallclockThresholdMillis;
     long mAppIdleParoleIntervalMillis;
     long mAppIdleParoleDurationMillis;
     long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
@@ -229,6 +227,7 @@
         // Get sync adapters for the authority
         String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
                 authority, userId);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
         for (String packageName: packages) {
             // Only force the sync adapters to active if the provider is not in the same package and
             // the sync adapter is a system package.
@@ -239,7 +238,12 @@
                     continue;
                 }
                 if (!packageName.equals(providerPkgName)) {
-                    setAppIdleAsync(packageName, false, userId);
+                    synchronized (mAppIdleLock) {
+                        int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId,
+                                    elapsedRealtime);
+                        maybeInformListeners(packageName, userId, elapsedRealtime,
+                                newBucket);
+                    }
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Shouldn't happen
@@ -493,11 +497,21 @@
             if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
                     || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
                     || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
-                    || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
-                mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+                    || event.mEventType == UsageEvents.Event.USER_INTERACTION
+                    || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN)) {
+
+                final int newBucket;
+                if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
+                    newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId,
+                            elapsedRealtime);
+                } else {
+                    newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
+                            elapsedRealtime);
+                }
+
+                maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+                        newBucket);
                 if (previouslyIdle) {
-                    maybeInformListeners(event.mPackage, userId, elapsedRealtime,
-                            AppStandby.STANDBY_BUCKET_ACTIVE);
                     notifyBatteryStats(event.mPackage, userId, false);
                 }
             }
@@ -896,14 +910,6 @@
         pw.println();
         pw.println("Settings:");
 
-        pw.print("  mAppIdleDurationMillis=");
-        TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
-        pw.println();
-
-        pw.print("  mAppIdleWallclockThresholdMillis=");
-        TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
-        pw.println();
-
         pw.print("  mCheckIdleIntervalMillis=");
         TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
         pw.println();
@@ -1033,6 +1039,11 @@
                 int userId) {
             return appWidgetManager.isBoundWidgetPackage(packageName, userId);
         }
+
+        String getAppIdleSettings() {
+            return Settings.Global.getString(mContext.getContentResolver(),
+                    Settings.Global.APP_IDLE_CONSTANTS);
+        }
     }
 
     class AppStandbyHandler extends Handler {
@@ -1165,31 +1176,18 @@
                 // Look at global settings for this.
                 // TODO: Maybe apply different thresholds for different users.
                 try {
-                    mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
-                            Settings.Global.APP_IDLE_CONSTANTS));
+                    mParser.setString(mInjector.getAppIdleSettings());
                 } catch (IllegalArgumentException e) {
                     Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
                     // fallthrough, mParser is empty and all defaults will be returned.
                 }
 
-                // Default: 12 hours of screen-on time sans dream-time
-                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
-                        COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
-                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
-                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
-                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
-                        COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
-
                 // Default: 24 hours between paroles
                 mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
                         COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
 
                 mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
                         COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
-                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
-                        mAppIdleScreenThresholdMillis);
 
                 String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
                 mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
@@ -1199,6 +1197,9 @@
                         null);
                 mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
                         ELAPSED_TIME_THRESHOLDS);
+                mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4,
+                        COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
+
             }
         }
 
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 0b10590..f02221c 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -643,6 +643,8 @@
                 return "SHORTCUT_INVOCATION";
             case UsageEvents.Event.CHOOSER_ACTION:
                 return "CHOOSER_ACTION";
+            case UsageEvents.Event.NOTIFICATION_SEEN:
+                return "NOTIFICATION_SEEN";
             default:
                 return "UNKNOWN";
         }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index f30d7bd..05480dc 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -1065,7 +1065,7 @@
      *
      * @param state The audio state of this {@code RemoteConnection}.
      * @hide
-     * @deprecated Use {@link #setCallAudioState(CallAudioState) instead.
+     * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead.
      */
     @SystemApi
     @Deprecated
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 6276626..c968406 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -101,9 +101,6 @@
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
      *
-     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
-     * not a 2 or 3-digit code.
-     *
      * @hide
      */
     public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr,
@@ -115,22 +112,29 @@
         // for inbound parcels
         mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
 
+        // Only allow INT_MAX if unknown string mcc/mnc
         if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
             mMccStr = mccStr;
-        } else if (mccStr.isEmpty()) {
-            // If the mccStr parsed from Parcel is empty, set it as null.
+        } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+            // If the mccStr is empty or unknown, set it as null.
             mMccStr = null;
         } else {
-            throw new IllegalArgumentException("invalid MCC format");
+            // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
+            // after the bug got fixed.
+            mMccStr = null;
+            log("invalid MCC format: " + mccStr);
         }
 
         if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
             mMncStr = mncStr;
-        } else if (mncStr.isEmpty()) {
-            // If the mncStr parsed from Parcel is empty, set it as null.
+        } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+            // If the mncStr is empty or unknown, set it as null.
             mMncStr = null;
         } else {
-            throw new IllegalArgumentException("invalid MNC format");
+            // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
+            // after the bug got fixed.
+            mMncStr = null;
+            log("invalid MNC format: " + mncStr);
         }
 
         mAlphaLong = alphal;
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 74d2966..825dcc3 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -102,9 +102,6 @@
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
      *
-     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
-     * not a 2 or 3-digit code.
-     *
      * @hide
      */
     public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr,
@@ -114,22 +111,29 @@
         mTac = tac;
         mEarfcn = earfcn;
 
+        // Only allow INT_MAX if unknown string mcc/mnc
         if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
             mMccStr = mccStr;
-        } else if (mccStr.isEmpty()) {
-            // If the mccStr parsed from Parcel is empty, set it as null.
+        } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+            // If the mccStr is empty or unknown, set it as null.
             mMccStr = null;
         } else {
-            throw new IllegalArgumentException("invalid MCC format");
+            // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
+            // after the bug got fixed.
+            mMccStr = null;
+            log("invalid MCC format: " + mccStr);
         }
 
         if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
             mMncStr = mncStr;
-        } else if (mncStr.isEmpty()) {
-            // If the mncStr parsed from Parcel is empty, set it as null.
+        } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+            // If the mncStr is empty or unknown, set it as null.
             mMncStr = null;
         } else {
-            throw new IllegalArgumentException("invalid MNC format");
+            // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
+            // after the bug got fixed.
+            mMncStr = null;
+            log("invalid MNC format: " + mncStr);
         }
 
         mAlphaLong = alphal;
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 51b11aa..e74b570 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -102,9 +102,6 @@
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
      *
-     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
-     * not a 2 or 3-digit code.
-     *
      * @hide
      */
     public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
@@ -114,22 +111,29 @@
         mPsc = psc;
         mUarfcn = uarfcn;
 
+        // Only allow INT_MAX if unknown string mcc/mnc
         if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
             mMccStr = mccStr;
-        } else if (mccStr.isEmpty()) {
-            // If the mccStr parsed from Parcel is empty, set it as null.
+        } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+            // If the mccStr is empty or unknown, set it as null.
             mMccStr = null;
         } else {
-            throw new IllegalArgumentException("invalid MCC format");
+            // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
+            // after the bug got fixed.
+            mMccStr = null;
+            log("invalid MCC format: " + mccStr);
         }
 
         if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
             mMncStr = mncStr;
-        } else if (mncStr.isEmpty()) {
-            // If the mncStr parsed from Parcel is empty, set it as null.
+        } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+            // If the mncStr is empty or unknown, set it as null.
             mMncStr = null;
         } else {
-            throw new IllegalArgumentException("invalid MNC format");
+            // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
+            // after the bug got fixed.
+            mMncStr = null;
+            log("invalid MNC format: " + mncStr);
         }
 
         mAlphaLong = alphal;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2f39ddb..1e6abf2 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -28,6 +28,7 @@
 import android.net.INetworkPolicyManager;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -361,6 +362,9 @@
 
     /**
      * TelephonyProvider column name for enable Volte.
+     *
+     * If this setting is not initialized (set to -1)  then we use the Carrier Config value
+     * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
      *@hide
      */
     public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
@@ -446,7 +450,15 @@
      * for #onSubscriptionsChanged to be invoked.
      */
     public static class OnSubscriptionsChangedListener {
-        private final Handler mHandler  = new Handler() {
+        private class OnSubscriptionsChangedListenerHandler extends Handler {
+            OnSubscriptionsChangedListenerHandler() {
+                super();
+            }
+
+            OnSubscriptionsChangedListenerHandler(Looper looper) {
+                super(looper);
+            }
+
             @Override
             public void handleMessage(Message msg) {
                 if (DBG) {
@@ -454,7 +466,22 @@
                 }
                 OnSubscriptionsChangedListener.this.onSubscriptionsChanged();
             }
-        };
+        }
+
+        private final Handler mHandler;
+
+        public OnSubscriptionsChangedListener() {
+            mHandler = new OnSubscriptionsChangedListenerHandler();
+        }
+
+        /**
+         * Allow a listener to be created with a custom looper
+         * @param looper the looper that the underlining handler should run on
+         * @hide
+         */
+        public OnSubscriptionsChangedListener(Looper looper) {
+            mHandler = new OnSubscriptionsChangedListenerHandler(looper);
+        }
 
         /**
          * Callback invoked when there is any change to any SubscriptionInfo. Typically
diff --git a/test-mock/api/android-test-mock-current.txt b/test-mock/api/android-test-mock-current.txt
index 93bbf6c..07fcee2 100644
--- a/test-mock/api/android-test-mock-current.txt
+++ b/test-mock/api/android-test-mock-current.txt
@@ -344,7 +344,6 @@
     method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int installExistingPackageAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void installPackage(android.net.Uri, android.content.pm.IPackageInstallObserver, int, java.lang.String);
     method public void installPackage(android.net.Uri, android.app.PackageInstallObserver, int, java.lang.String);
     method public boolean isInstantApp();
     method public boolean isInstantApp(java.lang.String);
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 7e08f51..0c562e6 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -26,12 +26,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
@@ -653,15 +652,6 @@
         throw new UnsupportedOperationException();
     }
 
-    /**
-     * @hide - to match hiding in superclass
-     */
-    @Override
-    public void installPackage(Uri packageURI, IPackageInstallObserver observer,
-            int flags, String installerPackageName) {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public void setInstallerPackageName(String targetPackage,
             String installerPackageName) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 08efc27..0f6fb50 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -218,6 +218,7 @@
       if (attr.id) {
         printer_->Print(attr.id.value().to_string());
       }
+      printer_->Println();
     }
   }
 
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 96a0203..6fcf0f6 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -268,6 +268,11 @@
   return out << res_id.to_string();
 }
 
+// For generic code to call 'using std::to_string; to_string(T);'.
+inline std::string to_string(const ResourceId& id) {
+  return id.to_string();
+}
+
 //
 // ResourceType implementation.
 //
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 66b5a7a..e94c0b4 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -16,7 +16,6 @@
 
 #include <sys/stat.h>
 
-#include <fstream>
 #include <queue>
 #include <unordered_map>
 #include <vector>
@@ -212,6 +211,8 @@
 // This delegate will attempt to masquerade any '@id/' references with ID 0xPPTTEEEE,
 // where PP > 7f, as 0x7fPPEEEE. Any potential overlapping is verified and an error occurs if such
 // an overlap exists.
+//
+// See b/37498913.
 class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate {
  public:
   FeatureSplitSymbolTableDelegate(IAaptContext* context) : context_(context) {
@@ -652,24 +653,26 @@
 static bool WriteStableIdMapToPath(IDiagnostics* diag,
                                    const std::unordered_map<ResourceName, ResourceId>& id_map,
                                    const std::string& id_map_path) {
-  std::ofstream fout(id_map_path, std::ofstream::binary);
-  if (!fout) {
-    diag->Error(DiagMessage(id_map_path) << strerror(errno));
+  io::FileOutputStream fout(id_map_path);
+  if (fout.HadError()) {
+    diag->Error(DiagMessage(id_map_path) << "failed to open: " << fout.GetError());
     return false;
   }
 
+  text::Printer printer(&fout);
   for (const auto& entry : id_map) {
     const ResourceName& name = entry.first;
     const ResourceId& id = entry.second;
-    fout << name << " = " << id << "\n";
+    printer.Print(name.to_string());
+    printer.Print(" = ");
+    printer.Println(id.to_string());
   }
+  fout.Flush();
 
-  if (!fout) {
-    diag->Error(DiagMessage(id_map_path) << "failed writing to file: "
-                                         << android::base::SystemErrorCodeToString(errno));
+  if (fout.HadError()) {
+    diag->Error(DiagMessage(id_map_path) << "failed writing to file: " << fout.GetError());
     return false;
   }
-
   return true;
 }
 
@@ -985,36 +988,35 @@
 
     file::AppendPath(&out_path, "R.java");
 
-    std::ofstream fout(out_path, std::ofstream::binary);
-    if (!fout) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed writing to '" << out_path
-                                        << "': " << android::base::SystemErrorCodeToString(errno));
+    io::FileOutputStream fout(out_path);
+    if (fout.HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path
+                                                      << "': " << fout.GetError());
       return false;
     }
 
-    std::unique_ptr<std::ofstream> fout_text;
+    std::unique_ptr<io::FileOutputStream> fout_text;
     if (out_text_symbols_path) {
-      fout_text =
-          util::make_unique<std::ofstream>(out_text_symbols_path.value(), std::ofstream::binary);
-      if (!*fout_text) {
-        context_->GetDiagnostics()->Error(
-            DiagMessage() << "failed writing to '" << out_text_symbols_path.value()
-                          << "': " << android::base::SystemErrorCodeToString(errno));
+      fout_text = util::make_unique<io::FileOutputStream>(out_text_symbols_path.value());
+      if (fout_text->HadError()) {
+        context_->GetDiagnostics()->Error(DiagMessage()
+                                          << "failed writing to '" << out_text_symbols_path.value()
+                                          << "': " << fout_text->GetError());
         return false;
       }
     }
 
     JavaClassGenerator generator(context_, table, java_options);
     if (!generator.Generate(package_name_to_generate, out_package, &fout, fout_text.get())) {
-      context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.getError());
+      context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.GetError());
       return false;
     }
 
-    if (!fout) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed writing to '" << out_path
-                                        << "': " << android::base::SystemErrorCodeToString(errno));
+    fout.Flush();
+
+    if (fout.HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path
+                                                      << "': " << fout.GetError());
       return false;
     }
     return true;
@@ -1142,18 +1144,19 @@
 
     file::AppendPath(&out_path, "Manifest.java");
 
-    std::ofstream fout(out_path, std::ofstream::binary);
-    if (!fout) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed writing to '" << out_path
-                                        << "': " << android::base::SystemErrorCodeToString(errno));
+    io::FileOutputStream fout(out_path);
+    if (fout.HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to open '" << out_path
+                                                      << "': " << fout.GetError());
       return false;
     }
 
-    if (!ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8, true, &fout)) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed writing to '" << out_path
-                                        << "': " << android::base::SystemErrorCodeToString(errno));
+    ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8, true, &fout);
+    fout.Flush();
+
+    if (fout.HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path
+                                                      << "': " << fout.GetError());
       return false;
     }
     return true;
@@ -1165,19 +1168,19 @@
     }
 
     const std::string& out_path = out.value();
-    std::ofstream fout(out_path, std::ofstream::binary);
-    if (!fout) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed to open '" << out_path
-                                        << "': " << android::base::SystemErrorCodeToString(errno));
+    io::FileOutputStream fout(out_path);
+    if (fout.HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to open '" << out_path
+                                                      << "': " << fout.GetError());
       return false;
     }
 
-    proguard::WriteKeepSet(&fout, keep_set);
-    if (!fout) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed writing to '" << out_path
-                                        << "': " << android::base::SystemErrorCodeToString(errno));
+    proguard::WriteKeepSet(keep_set, &fout);
+    fout.Flush();
+
+    if (fout.HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path
+                                                      << "': " << fout.GetError());
       return false;
     }
     return true;
diff --git a/tools/aapt2/io/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
index 4ff6d78..27529bc 100644
--- a/tools/aapt2/io/FileStream.cpp
+++ b/tools/aapt2/io/FileStream.cpp
@@ -25,6 +25,12 @@
 #include "android-base/macros.h"
 #include "android-base/utf8.h"
 
+#if defined(_WIN32)
+// This is only needed for O_CLOEXEC.
+#include <windows.h>
+#define O_CLOEXEC O_NOINHERIT
+#endif
+
 using ::android::base::SystemErrorCodeToString;
 using ::android::base::unique_fd;
 
@@ -32,18 +38,20 @@
 namespace io {
 
 FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
-    : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
-                      buffer_capacity) {
+    : buffer_capacity_(buffer_capacity) {
+  int mode = O_RDONLY | O_CLOEXEC | O_BINARY;
+  fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode)));
+  if (fd_ == -1) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
 }
 
 FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
-    : fd_(fd),
-      buffer_capacity_(buffer_capacity),
-      buffer_offset_(0u),
-      buffer_size_(0u),
-      total_byte_count_(0u) {
-  if (fd_ == -1) {
-    error_ = SystemErrorCodeToString(errno);
+    : fd_(fd), buffer_capacity_(buffer_capacity) {
+  if (fd_ < 0) {
+    error_ = "Bad File Descriptor";
   } else {
     buffer_.reset(new uint8_t[buffer_capacity_]);
   }
@@ -100,9 +108,16 @@
   return error_;
 }
 
-FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity)
-    : FileOutputStream(unique_fd(::android::base::utf8::open(path.c_str(), mode)),
-                       buffer_capacity) {
+FileOutputStream::FileOutputStream(const std::string& path, size_t buffer_capacity)
+    : buffer_capacity_(buffer_capacity) {
+  int mode = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY;
+  owned_fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode, 0666)));
+  fd_ = owned_fd_.get();
+  if (fd_ < 0) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
 }
 
 FileOutputStream::FileOutputStream(unique_fd fd, size_t buffer_capacity)
@@ -111,9 +126,9 @@
 }
 
 FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
-    : fd_(fd), buffer_capacity_(buffer_capacity), buffer_offset_(0u), total_byte_count_(0u) {
-  if (fd_ == -1) {
-    error_ = SystemErrorCodeToString(errno);
+    : fd_(fd), buffer_capacity_(buffer_capacity) {
+  if (fd_ < 0) {
+    error_ = "Bad File Descriptor";
   } else {
     buffer_.reset(new uint8_t[buffer_capacity_]);
   }
diff --git a/tools/aapt2/io/FileStream.h b/tools/aapt2/io/FileStream.h
index 4ed1ad5..62d910f 100644
--- a/tools/aapt2/io/FileStream.h
+++ b/tools/aapt2/io/FileStream.h
@@ -22,7 +22,6 @@
 #include <memory>
 #include <string>
 
-#include "android-base/file.h"  // for O_BINARY
 #include "android-base/macros.h"
 #include "android-base/unique_fd.h"
 
@@ -55,15 +54,15 @@
   android::base::unique_fd fd_;
   std::string error_;
   std::unique_ptr<uint8_t[]> buffer_;
-  size_t buffer_capacity_;
-  size_t buffer_offset_;
-  size_t buffer_size_;
-  size_t total_byte_count_;
+  size_t buffer_capacity_ = 0u;
+  size_t buffer_offset_ = 0u;
+  size_t buffer_size_ = 0u;
+  size_t total_byte_count_ = 0u;
 };
 
 class FileOutputStream : public OutputStream {
  public:
-  explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY,
+  explicit FileOutputStream(const std::string& path,
                             size_t buffer_capacity = kDefaultBufferCapacity);
 
   // Does not take ownership of `fd`.
@@ -97,9 +96,9 @@
   int fd_;
   std::string error_;
   std::unique_ptr<uint8_t[]> buffer_;
-  size_t buffer_capacity_;
-  size_t buffer_offset_;
-  size_t total_byte_count_;
+  size_t buffer_capacity_ = 0u;
+  size_t buffer_offset_ = 0u;
+  size_t total_byte_count_ = 0u;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/io/FileStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
index a6d58ca..c0eaa8e 100644
--- a/tools/aapt2/io/FileStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -16,6 +16,7 @@
 
 #include "io/FileStream.h"
 
+#include "android-base/file.h"
 #include "android-base/macros.h"
 #include "android-base/test_utils.h"
 
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index c93461a..8d91b00 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -23,6 +23,7 @@
 #include "text/Utf8Iterator.h"
 #include "util/Util.h"
 
+using ::aapt::text::Printer;
 using ::aapt::text::Utf8Iterator;
 using ::android::StringPiece;
 
@@ -109,23 +110,22 @@
   }
 }
 
-void AnnotationProcessor::WriteToStream(const StringPiece& prefix, std::ostream* out) const {
+void AnnotationProcessor::Print(Printer* printer) const {
   if (has_comments_) {
     std::string result = comment_.str();
     for (StringPiece line : util::Tokenize(result, '\n')) {
-      *out << prefix << line << "\n";
+      printer->Println(line);
     }
-    *out << prefix << " */"
-         << "\n";
+    printer->Println(" */");
   }
 
   if (annotation_bit_mask_ & AnnotationRule::kDeprecated) {
-    *out << prefix << "@Deprecated\n";
+    printer->Println("@Deprecated");
   }
 
   for (const AnnotationRule& rule : sAnnotationRules) {
     if (annotation_bit_mask_ & rule.bit_mask) {
-      *out << prefix << rule.annotation << "\n";
+      printer->Println(rule.annotation);
     }
   }
 }
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index a7bf73f..ae7bdb0 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -22,6 +22,8 @@
 
 #include "androidfw/StringPiece.h"
 
+#include "text/Printer.h"
+
 namespace aapt {
 
 // Builds a JavaDoc comment from a set of XML comments.
@@ -61,8 +63,8 @@
 
   void AppendNewLine();
 
-  // Writes the comments and annotations to the stream, with the given prefix before each line.
-  void WriteToStream(const android::StringPiece& prefix, std::ostream* out) const;
+  // Writes the comments and annotations to the Printer.
+  void Print(text::Printer* printer) const;
 
  private:
   std::stringstream comment_;
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 856f4cc..69f49c8 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -16,8 +16,12 @@
 
 #include "java/AnnotationProcessor.h"
 
+#include "io/StringStream.h"
 #include "test/Test.h"
+#include "text/Printer.h"
 
+using ::aapt::io::StringOutputStream;
+using ::aapt::text::Printer;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
@@ -33,9 +37,11 @@
   AnnotationProcessor processor;
   processor.AppendComment(comment);
 
-  std::stringstream result;
-  processor.WriteToStream("", &result);
-  std::string annotations = result.str();
+  std::string annotations;
+  StringOutputStream out(&annotations);
+  Printer printer(&out);
+  processor.Print(&printer);
+  out.Flush();
 
   EXPECT_THAT(annotations, HasSubstr("@Deprecated"));
 }
@@ -44,9 +50,11 @@
   AnnotationProcessor processor;
   processor.AppendComment("@SystemApi This is a system API");
 
-  std::stringstream result;
-  processor.WriteToStream("", &result);
-  std::string annotations = result.str();
+  std::string annotations;
+  StringOutputStream out(&annotations);
+  Printer printer(&out);
+  processor.Print(&printer);
+  out.Flush();
 
   EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi"));
   EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi")));
@@ -57,9 +65,11 @@
   AnnotationProcessor processor;
   processor.AppendComment("@TestApi This is a test API");
 
-  std::stringstream result;
-  processor.WriteToStream("", &result);
-  std::string annotations = result.str();
+  std::string annotations;
+  StringOutputStream out(&annotations);
+  Printer printer(&out);
+  processor.Print(&printer);
+  out.Flush();
 
   EXPECT_THAT(annotations, HasSubstr("@android.annotation.TestApi"));
   EXPECT_THAT(annotations, Not(HasSubstr("@TestApi")));
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 0c57e7e..b692ccf 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -18,25 +18,27 @@
 
 #include "androidfw/StringPiece.h"
 
+using ::aapt::text::Printer;
 using ::android::StringPiece;
 
 namespace aapt {
 
-void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const {
-  processor_.WriteToStream(prefix, out);
+void ClassMember::Print(bool /*final*/, Printer* printer) const {
+  processor_.Print(printer);
 }
 
 void MethodDefinition::AppendStatement(const StringPiece& statement) {
   statements_.push_back(statement.to_string());
 }
 
-void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final,
-                                     std::ostream* out) const {
-  *out << prefix << signature_ << " {\n";
+void MethodDefinition::Print(bool final, Printer* printer) const {
+  printer->Print(signature_).Println(" {");
+  printer->Indent();
   for (const auto& statement : statements_) {
-    *out << prefix << "  " << statement << "\n";
+    printer->Println(statement);
   }
-  *out << prefix << "}";
+  printer->Undent();
+  printer->Print("}");
 }
 
 ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) {
@@ -62,34 +64,32 @@
   return true;
 }
 
-void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final,
-                                    std::ostream* out) const {
+void ClassDefinition::Print(bool final, Printer* printer) const {
   if (empty() && !create_if_empty_) {
     return;
   }
 
-  ClassMember::WriteToStream(prefix, final, out);
+  ClassMember::Print(final, printer);
 
-  *out << prefix << "public ";
+  printer->Print("public ");
   if (qualifier_ == ClassQualifier::kStatic) {
-    *out << "static ";
+    printer->Print("static ");
   }
-  *out << "final class " << name_ << " {\n";
-
-  std::string new_prefix = prefix.to_string();
-  new_prefix.append(kIndent);
+  printer->Print("final class ").Print(name_).Println(" {");
+  printer->Indent();
 
   for (const std::unique_ptr<ClassMember>& member : ordered_members_) {
     // There can be nullptr members when a member is added to the ClassDefinition
     // and takes precedence over a previous member with the same name. The overridden member is
     // set to nullptr.
     if (member != nullptr) {
-      member->WriteToStream(new_prefix, final, out);
-      *out << "\n";
+      member->Print(final, printer);
+      printer->Println();
     }
   }
 
-  *out << prefix << "}";
+  printer->Undent();
+  printer->Print("}");
 }
 
 constexpr static const char* sWarningHeader =
@@ -100,11 +100,12 @@
     " * should not be modified by hand.\n"
     " */\n\n";
 
-bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package,
-                                    bool final, std::ostream* out) {
-  *out << sWarningHeader << "package " << package << ";\n\n";
-  def->WriteToStream("", final, out);
-  return bool(*out);
+void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package,
+                                    bool final, io::OutputStream* out) {
+  Printer printer(out);
+  printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
+  printer.Println();
+  def->Print(final, &printer);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 28a3489..fb11266 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -17,7 +17,6 @@
 #ifndef AAPT_JAVA_CLASSDEFINITION_H
 #define AAPT_JAVA_CLASSDEFINITION_H
 
-#include <ostream>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -27,6 +26,7 @@
 
 #include "Resource.h"
 #include "java/AnnotationProcessor.h"
+#include "text/Printer.h"
 #include "util/Util.h"
 
 namespace aapt {
@@ -47,11 +47,10 @@
 
   virtual const std::string& GetName() const = 0;
 
-  // Writes the class member to the out stream. Subclasses should derive this method
+  // Writes the class member to the Printer. Subclasses should derive this method
   // to write their own data. Call this base method from the subclass to write out
   // this member's comments/annotations.
-  virtual void WriteToStream(const android::StringPiece& prefix, bool final,
-                             std::ostream* out) const;
+  virtual void Print(bool final, text::Printer* printer) const;
 
  private:
   AnnotationProcessor processor_;
@@ -71,11 +70,16 @@
     return name_;
   }
 
-  void WriteToStream(const android::StringPiece& prefix, bool final,
-                     std::ostream* out) const override {
-    ClassMember::WriteToStream(prefix, final, out);
-    *out << prefix << "public static " << (final ? "final " : "") << "int " << name_ << "=" << val_
-         << ";";
+  void Print(bool final, text::Printer* printer) const override {
+    using std::to_string;
+
+    ClassMember::Print(final, printer);
+
+    printer->Print("public static ");
+    if (final) {
+      printer->Print("final ");
+    }
+    printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";");
   }
 
  private:
@@ -100,12 +104,14 @@
     return name_;
   }
 
-  void WriteToStream(const android::StringPiece& prefix, bool final,
-                     std::ostream* out) const override {
-    ClassMember::WriteToStream(prefix, final, out);
+  void Print(bool final, text::Printer* printer) const override {
+    ClassMember::Print(final, printer);
 
-    *out << prefix << "public static " << (final ? "final " : "") << "String "
-         << name_ << "=\"" << val_ << "\";";
+    printer->Print("public static ");
+    if (final) {
+      printer->Print("final ");
+    }
+    printer->Print("String ").Print(name_).Print("=\"").Print(val_).Print("\";");
   }
 
  private:
@@ -136,25 +142,27 @@
     return name_;
   }
 
-  void WriteToStream(const android::StringPiece& prefix, bool final,
-                     std::ostream* out) const override {
-    ClassMember::WriteToStream(prefix, final, out);
+  void Print(bool final, text::Printer* printer) const override {
+    ClassMember::Print(final, printer);
 
-    *out << prefix << "public static final int[] " << name_ << "={";
+    printer->Print("public static final int[] ").Print(name_).Print("={");
+    printer->Indent();
 
     const auto begin = elements_.begin();
     const auto end = elements_.end();
     for (auto current = begin; current != end; ++current) {
       if (std::distance(begin, current) % kAttribsPerLine == 0) {
-        *out << "\n" << prefix << kIndent << kIndent;
+        printer->Println();
       }
 
-      *out << *current;
+      printer->Print(to_string(*current));
       if (std::distance(current, end) > 1) {
-        *out << ", ";
+        printer->Print(", ");
       }
     }
-    *out << "\n" << prefix << kIndent << "};";
+    printer->Println();
+    printer->Undent();
+    printer->Print("};");
   }
 
  private:
@@ -187,8 +195,7 @@
     return false;
   }
 
-  void WriteToStream(const android::StringPiece& prefix, bool final,
-                     std::ostream* out) const override;
+  void Print(bool final, text::Printer* printer) const override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MethodDefinition);
@@ -201,8 +208,8 @@
 
 class ClassDefinition : public ClassMember {
  public:
-  static bool WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
-                            bool final, std::ostream* out);
+  static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
+                            bool final, io::OutputStream* out);
 
   ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
       : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
@@ -220,8 +227,7 @@
     return name_;
   }
 
-  void WriteToStream(const android::StringPiece& prefix, bool final,
-                     std::ostream* out) const override;
+  void Print(bool final, text::Printer* printer) const override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 91cef64..9861770 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -37,8 +37,10 @@
 #include "java/ClassDefinition.h"
 #include "process/SymbolTable.h"
 
-using android::StringPiece;
-using android::base::StringPrintf;
+using ::aapt::io::OutputStream;
+using ::aapt::text::Printer;
+using ::android::StringPiece;
+using ::android::base::StringPrintf;
 
 namespace aapt {
 
@@ -230,7 +232,7 @@
                                           const StringPiece& package_name_to_generate,
                                           ClassDefinition* out_class_def,
                                           MethodDefinition* out_rewrite_method,
-                                          std::ostream* out_r_txt) {
+                                          Printer* r_txt_printer) {
   const std::string array_field_name = TransformToFieldName(name.entry);
   std::unique_ptr<ResourceArrayMember> array_def =
       util::make_unique<ResourceArrayMember>(array_field_name);
@@ -323,8 +325,8 @@
     array_def->GetCommentBuilder()->AppendComment(styleable_comment.str());
   }
 
-  if (out_r_txt != nullptr) {
-    *out_r_txt << "int[] styleable " << array_field_name << " {";
+  if (r_txt_printer != nullptr) {
+    r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {");
   }
 
   // Add the ResourceIds to the array member.
@@ -332,16 +334,16 @@
     const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
     array_def->AddElement(id);
 
-    if (out_r_txt != nullptr) {
+    if (r_txt_printer != nullptr) {
       if (i != 0) {
-        *out_r_txt << ",";
+        r_txt_printer->Print(",");
       }
-      *out_r_txt << " " << id;
+      r_txt_printer->Print(" ").Print(id.to_string());
     }
   }
 
-  if (out_r_txt != nullptr) {
-    *out_r_txt << " }\n";
+  if (r_txt_printer != nullptr) {
+    r_txt_printer->Println(" }");
   }
 
   // Add the Styleable array to the Styleable class.
@@ -396,9 +398,9 @@
     attr_processor->AppendComment(
         StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data()));
 
-    if (out_r_txt != nullptr) {
-      *out_r_txt << StringPrintf("int styleable %s %d\n", sorted_attributes[i].field_name.data(),
-                                 (int)i);
+    if (r_txt_printer != nullptr) {
+      r_txt_printer->Println(
+          StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i));
     }
 
     out_class_def->AddMember(std::move(index_member));
@@ -422,10 +424,12 @@
 void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
                                          const ResourceEntry& entry, ClassDefinition* out_class_def,
                                          MethodDefinition* out_rewrite_method,
-                                         std::ostream* out_r_txt) {
+                                         text::Printer* r_txt_printer) {
   ResourceId real_id = id;
   if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId &&
       id.package_id() > kAppPackageId) {
+    // Workaround for feature splits using package IDs > 0x7F.
+    // See b/37498913.
     real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id());
   }
 
@@ -456,8 +460,13 @@
 
   out_class_def->AddMember(std::move(resource_member));
 
-  if (out_r_txt != nullptr) {
-    *out_r_txt << "int " << name.type << " " << field_name << " " << real_id << "\n";
+  if (r_txt_printer != nullptr) {
+    r_txt_printer->Print("int ")
+        .Print(to_string(name.type))
+        .Print(" ")
+        .Print(field_name)
+        .Print(" ")
+        .Println(real_id.to_string());
   }
 
   if (out_rewrite_method != nullptr) {
@@ -497,7 +506,7 @@
                                      const ResourceTableType& type,
                                      ClassDefinition* out_type_class_def,
                                      MethodDefinition* out_rewrite_method_def,
-                                     std::ostream* out_r_txt) {
+                                     Printer* r_txt_printer) {
   for (const auto& entry : type.entries) {
     const Maybe<std::string> unmangled_name =
         UnmangleResource(package.name, package_name_to_generate, *entry);
@@ -532,18 +541,18 @@
           static_cast<const Styleable*>(entry->values.front()->value.get());
 
       ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
-                       out_rewrite_method_def, out_r_txt);
+                       out_rewrite_method_def, r_txt_printer);
     } else {
       ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
-                      out_r_txt);
+                      r_txt_printer);
     }
   }
   return true;
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out,
-                                  std::ostream* out_r_txt) {
-  return Generate(package_name_to_generate, package_name_to_generate, out);
+bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
+                                  OutputStream* out_r_txt) {
+  return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
 }
 
 static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations,
@@ -556,11 +565,16 @@
 }
 
 bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
-                                  const StringPiece& out_package_name, std::ostream* out,
-                                  std::ostream* out_r_txt) {
+                                  const StringPiece& out_package_name, OutputStream* out,
+                                  OutputStream* out_r_txt) {
   ClassDefinition r_class("R", ClassQualifier::kNone, true);
   std::unique_ptr<MethodDefinition> rewrite_method;
 
+  std::unique_ptr<Printer> r_txt_printer;
+  if (out_r_txt != nullptr) {
+    r_txt_printer = util::make_unique<Printer>(out_r_txt);
+  }
+
   // Generate an onResourcesLoaded() callback if requested.
   if (options_.rewrite_callback_options) {
     rewrite_method =
@@ -586,7 +600,7 @@
       std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>(
           to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty);
       if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(),
-                       rewrite_method.get(), out_r_txt)) {
+                       rewrite_method.get(), r_txt_printer.get())) {
         return false;
       }
 
@@ -595,7 +609,7 @@
         const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
         if (priv_type) {
           if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
-                           rewrite_method.get(), out_r_txt)) {
+                           rewrite_method.get(), r_txt_printer.get())) {
             return false;
           }
         }
@@ -619,22 +633,7 @@
   }
 
   AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder());
-
-  if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) {
-    return false;
-  }
-
-  out->flush();
-
-  if (out_r_txt != nullptr) {
-    out_r_txt->flush();
-
-    if (!*out_r_txt) {
-      error_ = android::base::SystemErrorCodeToString(errno);
-      return false;
-    }
-  }
-
+  ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out);
   return true;
 }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 2541749..4992f07 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -17,16 +17,16 @@
 #ifndef AAPT_JAVA_CLASS_GENERATOR_H
 #define AAPT_JAVA_CLASS_GENERATOR_H
 
-#include <ostream>
 #include <string>
 
 #include "androidfw/StringPiece.h"
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "androidfw/StringPiece.h"
+#include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
+#include "text/Printer.h"
 
 namespace aapt {
 
@@ -70,14 +70,14 @@
   // All symbols technically belong to a single package, but linked libraries will
   // have their names mangled, denoting that they came from a different package.
   // We need to generate these symbols in a separate file. Returns true on success.
-  bool Generate(const android::StringPiece& package_name_to_generate, std::ostream* out,
-                std::ostream* out_r_txt = nullptr);
+  bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out,
+                io::OutputStream* out_r_txt = nullptr);
 
   bool Generate(const android::StringPiece& package_name_to_generate,
-                const android::StringPiece& output_package_name, std::ostream* out,
-                std::ostream* out_r_txt = nullptr);
+                const android::StringPiece& output_package_name, io::OutputStream* out,
+                io::OutputStream* out_r_txt = nullptr);
 
-  const std::string& getError() const;
+  const std::string& GetError() const;
 
   static std::string TransformToFieldName(const android::StringPiece& symbol);
 
@@ -94,13 +94,13 @@
   bool ProcessType(const android::StringPiece& package_name_to_generate,
                    const ResourceTablePackage& package, const ResourceTableType& type,
                    ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def,
-                   std::ostream* out_r_txt);
+                   text::Printer* r_txt_printer);
 
   // Writes a resource to the R.java file, optionally writing out a rewrite rule for its package
   // ID if `out_rewrite_method` is not nullptr.
   void ProcessResource(const ResourceNameRef& name, const ResourceId& id,
                        const ResourceEntry& entry, ClassDefinition* out_class_def,
-                       MethodDefinition* out_rewrite_method, std::ostream* out_r_txt);
+                       MethodDefinition* out_rewrite_method, text::Printer* r_txt_printer);
 
   // Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for
   // its package ID if `out_rewrite_method` is not nullptr.
@@ -109,7 +109,7 @@
                         const Styleable& styleable,
                         const android::StringPiece& package_name_to_generate,
                         ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method,
-                        std::ostream* out_r_txt);
+                        text::Printer* r_txt_printer);
 
   IAaptContext* context_;
   ResourceTable* table_;
@@ -117,7 +117,7 @@
   std::string error_;
 };
 
-inline const std::string& JavaClassGenerator::getError() const {
+inline const std::string& JavaClassGenerator::GetError() const {
   return error_;
 }
 
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 668e434..02f4cb1 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -16,12 +16,13 @@
 
 #include "java/JavaClassGenerator.h"
 
-#include <sstream>
 #include <string>
 
+#include "io/StringStream.h"
 #include "test/Test.h"
 #include "util/Util.h"
 
+using ::aapt::io::StringOutputStream;
 using ::android::StringPiece;
 using ::testing::HasSubstr;
 using ::testing::Lt;
@@ -45,7 +46,8 @@
           .Build();
   JavaClassGenerator generator(context.get(), table.get(), {});
 
-  std::stringstream out;
+  std::string result;
+  StringOutputStream out(&result);
   EXPECT_FALSE(generator.Generate("android", &out));
 }
 
@@ -69,10 +71,10 @@
           .Build();
   JavaClassGenerator generator(context.get(), table.get(), {});
 
-  std::stringstream out;
+  std::string output;
+  StringOutputStream out(&output);
   EXPECT_TRUE(generator.Generate("android", &out));
-
-  std::string output = out.str();
+  out.Flush();
 
   EXPECT_THAT(output, HasSubstr("public static final int hey_man=0x01020000;"));
   EXPECT_THAT(output, HasSubstr("public static final int[] hey_dude={"));
@@ -93,10 +95,12 @@
           .SetNameManglerPolicy(NameManglerPolicy{"android"})
           .Build();
   JavaClassGenerator generator(context.get(), table.get(), {});
-  std::stringstream out;
-  ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out));
 
-  std::string output = out.str();
+  std::string output;
+  StringOutputStream out(&output);
+  ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out));
+  out.Flush();
+
   EXPECT_THAT(output, HasSubstr("package com.android.internal;"));
   EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;"));
   EXPECT_THAT(output, Not(HasSubstr("two")));
@@ -117,10 +121,12 @@
           .SetNameManglerPolicy(NameManglerPolicy{"android"})
           .Build();
   JavaClassGenerator generator(context.get(), table.get(), {});
-  std::stringstream out;
-  ASSERT_TRUE(generator.Generate("android", &out));
 
-  std::string output = out.str();
+  std::string output;
+  StringOutputStream out(&output);
+  ASSERT_TRUE(generator.Generate("android", &out));
+  out.Flush();
+
   EXPECT_THAT(output, HasSubstr("public static final class attr"));
   EXPECT_THAT(output, Not(HasSubstr("public static final class ^attr-private")));
 }
@@ -147,9 +153,11 @@
   options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
   {
     JavaClassGenerator generator(context.get(), table.get(), options);
-    std::stringstream out;
+    std::string output;
+    StringOutputStream out(&output);
     ASSERT_TRUE(generator.Generate("android", &out));
-    std::string output = out.str();
+    out.Flush();
+
     EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;"));
     EXPECT_THAT(output, Not(HasSubstr("two")));
     EXPECT_THAT(output, Not(HasSubstr("three")));
@@ -158,9 +166,11 @@
   options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
   {
     JavaClassGenerator generator(context.get(), table.get(), options);
-    std::stringstream out;
+    std::string output;
+    StringOutputStream out(&output);
     ASSERT_TRUE(generator.Generate("android", &out));
-    std::string output = out.str();
+    out.Flush();
+
     EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;"));
     EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;"));
     EXPECT_THAT(output, Not(HasSubstr("three")));
@@ -169,9 +179,11 @@
   options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
   {
     JavaClassGenerator generator(context.get(), table.get(), options);
-    std::stringstream out;
+    std::string output;
+    StringOutputStream out(&output);
     ASSERT_TRUE(generator.Generate("android", &out));
-    std::string output = out.str();
+    out.Flush();
+
     EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;"));
     EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;"));
     EXPECT_THAT(output, HasSubstr("public static final int three=0x01020002;"));
@@ -235,10 +247,11 @@
           .Build();
   JavaClassGenerator generator(context.get(), table.get(), {});
 
-  std::stringstream out;
+  std::string output;
+  StringOutputStream out(&output);
   EXPECT_TRUE(generator.Generate("android", &out));
+  out.Flush();
 
-  std::string output = out.str();
   EXPECT_THAT(output, HasSubstr("int foo_bar="));
   EXPECT_THAT(output, HasSubstr("int foo_com_lib_bar="));
 }
@@ -258,9 +271,11 @@
           .SetNameManglerPolicy(NameManglerPolicy{"android"})
           .Build();
   JavaClassGenerator generator(context.get(), table.get(), {});
-  std::stringstream out;
+
+  std::string output;
+  StringOutputStream out(&output);
   ASSERT_TRUE(generator.Generate("android", &out));
-  std::string output = out.str();
+  out.Flush();
 
   const char* expected_text =
       R"EOF(/**
@@ -298,9 +313,11 @@
   JavaClassGeneratorOptions options;
   options.use_final = false;
   JavaClassGenerator generator(context.get(), table.get(), options);
-  std::stringstream out;
+
+  std::string output;
+  StringOutputStream out(&output);
   ASSERT_TRUE(generator.Generate("android", &out));
-  std::string output = out.str();
+  out.Flush();
 
   EXPECT_THAT(output, HasSubstr("attr name android:one"));
   EXPECT_THAT(output, HasSubstr("attr description"));
@@ -332,9 +349,11 @@
 
   JavaClassGeneratorOptions options;
   JavaClassGenerator generator(context.get(), table.get(), {});
-  std::stringstream out;
+
+  std::string output;
+  StringOutputStream out(&output);
   ASSERT_TRUE(generator.Generate("android", &out));
-  std::string output = out.str();
+  out.Flush();
 
   std::string::size_type actionbar_pos = output.find("int[] ActionBar");
   ASSERT_THAT(actionbar_pos, Ne(std::string::npos));
@@ -373,9 +392,11 @@
   JavaClassGeneratorOptions options;
   options.use_final = false;
   JavaClassGenerator generator(context.get(), table.get(), options);
-  std::stringstream out;
+
+  std::string output;
+  StringOutputStream out(&output);
   ASSERT_TRUE(generator.Generate("android", &out));
-  std::string output = out.str();
+  out.Flush();
 
   EXPECT_THAT(output, Not(HasSubstr("@attr name android:one")));
   EXPECT_THAT(output, Not(HasSubstr("@attr description")));
@@ -409,10 +430,10 @@
   options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{{"com.foo", "com.boo"}};
   JavaClassGenerator generator(context.get(), table.get(), options);
 
-  std::stringstream out;
+  std::string output;
+  StringOutputStream out(&output);
   ASSERT_TRUE(generator.Generate("android", &out));
-
-  std::string output = out.str();
+  out.Flush();
 
   EXPECT_THAT(output, HasSubstr("void onResourcesLoaded"));
   EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded"));
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index b12202a..3f6645f 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -23,8 +23,7 @@
 
 namespace aapt {
 
-std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag,
-                                                       xml::XmlResource* res);
+std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index ada5634..c324238 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -16,8 +16,10 @@
 
 #include "java/ManifestClassGenerator.h"
 
+#include "io/StringStream.h"
 #include "test/Test.h"
 
+using ::aapt::io::StringOutputStream;
 using ::testing::HasSubstr;
 using ::testing::Not;
 
@@ -144,12 +146,9 @@
     return ::testing::AssertionFailure() << "manifest_class == nullptr";
   }
 
-  std::stringstream out;
-  if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out)) {
-    return ::testing::AssertionFailure() << "failed to write java file";
-  }
-
-  *out_str = out.str();
+  StringOutputStream out(out_str);
+  manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out);
+  out.Flush();
   return ::testing::AssertionSuccess();
 }
 
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index b214d21..132b234 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -20,14 +20,18 @@
 #include <string>
 
 #include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
 
 #include "JavaClassGenerator.h"
 #include "ResourceUtils.h"
 #include "ValueVisitor.h"
-#include "androidfw/StringPiece.h"
+#include "text/Printer.h"
 #include "util/Util.h"
 #include "xml/XmlDom.h"
 
+using ::aapt::io::OutputStream;
+using ::aapt::text::Printer;
+
 namespace aapt {
 namespace proguard {
 
@@ -326,12 +330,13 @@
   return true;
 }
 
-bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
+void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) {
+  Printer printer(out);
   for (const auto& entry : keep_set.manifest_class_set_) {
     for (const UsageLocation& location : entry.second) {
-      *out << "# Referenced at " << location.source << "\n";
+      printer.Print("# Referenced at ").Println(location.source.to_string());
     }
-    *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+    printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
   }
 
   for (const auto& entry : keep_set.conditional_class_set_) {
@@ -342,26 +347,31 @@
     }
 
     for (const UsageLocation& location : entry.second) {
-      *out << "# Referenced at " << location.source << "\n";
+      printer.Print("# Referenced at ").Println(location.source.to_string());
     }
     if (keep_set.conditional_keep_rules_ && can_be_conditional) {
-      *out << "-if class **.R$layout {\n";
+      printer.Println("-if class **.R$layout {");
+      printer.Indent();
       for (const UsageLocation& location : locations) {
-        auto transformed_name = JavaClassGenerator::TransformToFieldName(location.name.entry);
-        *out << "  int " << transformed_name << ";\n";
+        printer.Print("int ")
+            .Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
+            .Println(";");
       }
-      *out << "}\n";
+      printer.Undent();
+      printer.Println("}");
+      printer.Println();
     }
-    *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+    printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
+    printer.Println();
   }
 
   for (const auto& entry : keep_set.method_set_) {
     for (const UsageLocation& location : entry.second) {
-      *out << "# Referenced at " << location.source << "\n";
+      printer.Print("# Referenced at ").Println(location.source.to_string());
     }
-    *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
+    printer.Print("-keepclassmembers class * { *** ").Print(entry.first).Println("(...); }");
+    printer.Println();
   }
-  return true;
 }
 
 bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 8dbe3c2..46827ee 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -22,11 +22,13 @@
 #include <set>
 #include <string>
 
+#include "androidfw/StringPiece.h"
+
 #include "Resource.h"
 #include "ResourceTable.h"
 #include "Source.h"
 #include "ValueVisitor.h"
-#include "androidfw/StringPiece.h"
+#include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
 
@@ -62,7 +64,7 @@
   }
 
  private:
-  friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
+  friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out);
 
   friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
                                std::set<UsageLocation>* locations);
@@ -76,11 +78,12 @@
 
 bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set,
                                      bool main_dex_only = false);
-bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set);
-bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
-                               KeepSet* keep_set);
 
-bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
+bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set);
+
+bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set);
+
+void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out);
 
 bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
                       std::set<UsageLocation>* locations);
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 802c56a..37d1a5f 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -17,13 +17,23 @@
 #include "java/ProguardRules.h"
 #include "link/Linkers.h"
 
+#include "io/StringStream.h"
 #include "test/Test.h"
 
+using ::aapt::io::StringOutputStream;
 using ::testing::HasSubstr;
 using ::testing::Not;
 
 namespace aapt {
 
+std::string GetKeepSetString(const proguard::KeepSet& set) {
+  std::string out;
+  StringOutputStream sout(&out);
+  proguard::WriteKeepSet(set, &sout);
+  sout.Flush();
+  return out;
+}
+
 TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
@@ -34,10 +44,8 @@
   proguard::KeepSet set;
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
 }
 
@@ -50,10 +58,8 @@
   proguard::KeepSet set;
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
 }
 
@@ -68,10 +74,8 @@
   proguard::KeepSet set;
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
   EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
 }
@@ -87,10 +91,8 @@
   proguard::KeepSet set;
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
 }
 
@@ -126,11 +128,10 @@
   ASSERT_TRUE(proguard::CollectProguardRules(bar_layout.get(), &set));
   ASSERT_TRUE(proguard::CollectProguardRules(foo_layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
+  EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
   EXPECT_THAT(actual, HasSubstr("int foo"));
   EXPECT_THAT(actual, HasSubstr("int bar"));
   EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
@@ -148,10 +149,9 @@
   set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name);
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
+  EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
   EXPECT_THAT(actual, HasSubstr("int foo"));
   EXPECT_THAT(actual, HasSubstr("int bar"));
@@ -170,11 +170,10 @@
   set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name);
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, Not(HasSubstr("-if")));
+  EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
 }
 
 TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
@@ -187,10 +186,8 @@
   proguard::KeepSet set;
   ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("bar_method"));
 }
 
@@ -208,10 +205,8 @@
   proguard::KeepSet set;
   ASSERT_TRUE(proguard::CollectProguardRules(menu.get(), &set));
 
-  std::stringstream out;
-  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+  std::string actual = GetKeepSetString(set);
 
-  std::string actual = out.str();
   EXPECT_THAT(actual, HasSubstr("on_click"));
   EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
   EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index 38b3585..243800c 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -26,18 +26,18 @@
 namespace aapt {
 namespace text {
 
-void Printer::Println(const StringPiece& str) {
+Printer& Printer::Println(const StringPiece& str) {
   Print(str);
-  Print("\n");
+  return Print("\n");
 }
 
-void Printer::Println() {
-  Print("\n");
+Printer& Printer::Println() {
+  return Print("\n");
 }
 
-void Printer::Print(const StringPiece& str) {
+Printer& Printer::Print(const StringPiece& str) {
   if (error_) {
-    return;
+    return *this;
   }
 
   auto remaining_str_begin = str.begin();
@@ -53,7 +53,7 @@
         for (int i = 0; i < indent_level_; i++) {
           if (!io::Copy(out_, "  ")) {
             error_ = true;
-            return;
+            return *this;
           }
         }
         needs_indent_ = false;
@@ -61,7 +61,7 @@
 
       if (!io::Copy(out_, str_to_copy)) {
         error_ = true;
-        return;
+        return *this;
       }
     }
 
@@ -69,7 +69,7 @@
     if (new_line_iter != remaining_str_end) {
       if (!io::Copy(out_, "\n")) {
         error_ = true;
-        return;
+        return *this;
       }
       needs_indent_ = true;
       // Ok to increment iterator here because we know that the '\n' character is one byte.
@@ -78,6 +78,7 @@
       remaining_str_begin = new_line_iter;
     }
   }
+  return *this;
 }
 
 void Printer::Indent() {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index 94b3c0b..f399f8e 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -31,9 +31,9 @@
   explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
   }
 
-  void Print(const ::android::StringPiece& str);
-  void Println(const ::android::StringPiece& str);
-  void Println();
+  Printer& Print(const ::android::StringPiece& str);
+  Printer& Println(const ::android::StringPiece& str);
+  Printer& Println();
 
   void Indent();
   void Undent();
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index c9f3199..fd42033 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -72,6 +72,9 @@
 
         self.ident = self.raw.replace(" deprecated ", " ")
 
+    def __hash__(self):
+        return hash(self.raw)
+
     def __repr__(self):
         return self.raw
 
@@ -110,6 +113,9 @@
             ident = ident[:ident.index(" throws ")]
         self.ident = ident
 
+    def __hash__(self):
+        return hash(self.raw)
+
     def __repr__(self):
         return self.raw
 
@@ -145,6 +151,9 @@
 
         self.name = self.fullname[self.fullname.rindex(".")+1:]
 
+    def __hash__(self):
+        return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
+
     def __repr__(self):
         return self.raw
 
@@ -256,6 +265,14 @@
     _fail(clazz, detail, True, rule, msg)
 
 
+noticed = {}
+
+def notice(clazz):
+    global noticed
+
+    noticed[clazz.fullname] = hash(clazz)
+
+
 def verify_constants(clazz):
     """All static final constants must be FOO_NAME style."""
     if re.match("android\.R\.[a-z]+", clazz.fullname): return
@@ -1203,6 +1220,9 @@
 
 def examine_clazz(clazz):
     """Find all style issues in the given class."""
+
+    notice(clazz)
+
     if clazz.pkg.name.startswith("java"): return
     if clazz.pkg.name.startswith("junit"): return
     if clazz.pkg.name.startswith("org.apache"): return
@@ -1258,10 +1278,11 @@
 
 def examine_stream(stream):
     """Find all style issues in the given API stream."""
-    global failures
+    global failures, noticed
     failures = {}
+    noticed = {}
     _parse_stream(stream, examine_clazz)
-    return failures
+    return (failures, noticed)
 
 
 def examine_api(api):
@@ -1338,6 +1359,8 @@
             help="Disable terminal colors")
     parser.add_argument("--allow-google", action='store_const', const=True,
             help="Allow references to Google")
+    parser.add_argument("--show-noticed", action='store_const', const=True,
+            help="Show API changes noticed")
     args = vars(parser.parse_args())
 
     if args['no_color']:
@@ -1350,16 +1373,21 @@
     previous_file = args['previous.txt']
 
     with current_file as f:
-        cur_fail = examine_stream(f)
+        cur_fail, cur_noticed = examine_stream(f)
     if not previous_file is None:
         with previous_file as f:
-            prev_fail = examine_stream(f)
+            prev_fail, prev_noticed = examine_stream(f)
 
         # ignore errors from previous API level
         for p in prev_fail:
             if p in cur_fail:
                 del cur_fail[p]
 
+        # ignore classes unchanged from previous API level
+        for k, v in prev_noticed.iteritems():
+            if k in cur_noticed and v == cur_noticed[k]:
+                del cur_noticed[k]
+
         """
         # NOTE: disabled because of memory pressure
         # look for compatibility issues
@@ -1371,6 +1399,12 @@
             print
         """
 
+    if args['show_noticed'] and len(cur_noticed) != 0:
+        print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
+        for f in sorted(cur_noticed.keys()):
+            print f
+        print
+
     if len(cur_fail) != 0:
         print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
         for f in sorted(cur_fail):
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_linter.py
similarity index 84%
rename from tools/fonts/fontchain_lint.py
rename to tools/fonts/fontchain_linter.py
index dcb90e4..15d39fd 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_linter.py
@@ -163,11 +163,14 @@
             'U+%04X was not found in %s' % (char, font))
 
 
-def assert_font_supports_none_of_chars(font, chars):
+def assert_font_supports_none_of_chars(font, chars, fallbackName):
     best_cmap = get_best_cmap(font)
     for char in chars:
-        assert char not in best_cmap, (
-            'U+%04X was found in %s' % (char, font))
+        if fallbackName:
+            assert char not in best_cmap, 'U+%04X was found in %s' % (char, font)
+        else:
+            assert char not in best_cmap, (
+                'U+%04X was found in %s in fallback %s' % (char, font, fallbackName))
 
 
 def assert_font_supports_all_sequences(font, sequences):
@@ -196,19 +199,21 @@
 
 
 class FontRecord(object):
-    def __init__(self, name, scripts, variant, weight, style, font):
+    def __init__(self, name, scripts, variant, weight, style, fallback_for, font):
         self.name = name
         self.scripts = scripts
         self.variant = variant
         self.weight = weight
         self.style = style
+        self.fallback_for = fallback_for
         self.font = font
 
 
 def parse_fonts_xml(fonts_xml_path):
-    global _script_to_font_map, _fallback_chain
+    global _script_to_font_map, _fallback_chains, _all_fonts
     _script_to_font_map = collections.defaultdict(set)
-    _fallback_chain = []
+    _fallback_chains = {}
+    _all_fonts = []
     tree = ElementTree.parse(fonts_xml_path)
     families = tree.findall('family')
     # Minikin supports up to 254 but users can place their own font at the first
@@ -225,10 +230,17 @@
                 'No variant expected for LGC font %s.' % name)
             assert langs is None, (
                 'No language expected for LGC fonts %s.' % name)
+            assert name not in _fallback_chains, 'Duplicated name entry %s' % name
+            _fallback_chains[name] = []
         else:
             assert variant in {None, 'elegant', 'compact'}, (
                 'Unexpected value for variant: %s' % variant)
 
+    for family in families:
+        name = family.get('name')
+        variant = family.get('variant')
+        langs = family.get('lang')
+
         if langs:
             langs = langs.split()
             scripts = {lang_to_script(lang) for lang in langs}
@@ -247,17 +259,36 @@
             assert style in {'normal', 'italic'}, (
                 'Unknown style "%s"' % style)
 
+            fallback_for = child.get('fallbackFor')
+
+            assert not name or not fallback_for, (
+                'name and fallbackFor cannot be present at the same time')
+            assert not fallback_for or fallback_for in _fallback_chains, (
+                'Unknown fallback name: %s' % fallback_for)
+
             index = child.get('index')
             if index:
                 index = int(index)
 
-            _fallback_chain.append(FontRecord(
+            record = FontRecord(
                 name,
                 frozenset(scripts),
                 variant,
                 weight,
                 style,
-                (font_file, index)))
+                fallback_for,
+                (font_file, index))
+
+            _all_fonts.append(record)
+
+            if not fallback_for:
+                if not name or name == 'sans-serif':
+                    for _, fallback in _fallback_chains.iteritems():
+                        fallback.append(record)
+                else:
+                    _fallback_chains[name].append(record)
+            else:
+                _fallback_chains[fallback_for].append(record)
 
             if name: # non-empty names are used for default LGC fonts
                 map_scripts = {'Latn', 'Grek', 'Cyrl'}
@@ -274,7 +305,7 @@
 
 def get_emoji_font():
     emoji_fonts = [
-        record.font for record in _fallback_chain
+        record.font for record in _all_fonts
         if 'Zsye' in record.scripts]
     assert len(emoji_fonts) == 1, 'There are %d emoji fonts.' % len(emoji_fonts)
     return emoji_fonts[0]
@@ -318,35 +349,36 @@
 
 def check_emoji_defaults(default_emoji):
     missing_text_chars = _emoji_properties['Emoji'] - default_emoji
-    emoji_font_seen = False
-    for record in _fallback_chain:
-        if 'Zsye' in record.scripts:
-            emoji_font_seen = True
-            # No need to check the emoji font
-            continue
-        # For later fonts, we only check them if they have a script
-        # defined, since the defined script may get them to a higher
-        # score even if they appear after the emoji font. However,
-        # we should skip checking the text symbols font, since
-        # symbol fonts should be able to override the emoji display
-        # style when 'Zsym' is explicitly specified by the user.
-        if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
-            continue
+    for name, fallback_chain in _fallback_chains.iteritems():
+        emoji_font_seen = False
+        for record in fallback_chain:
+            if 'Zsye' in record.scripts:
+                emoji_font_seen = True
+                # No need to check the emoji font
+                continue
+            # For later fonts, we only check them if they have a script
+            # defined, since the defined script may get them to a higher
+            # score even if they appear after the emoji font. However,
+            # we should skip checking the text symbols font, since
+            # symbol fonts should be able to override the emoji display
+            # style when 'Zsym' is explicitly specified by the user.
+            if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
+                continue
 
-        # Check default emoji-style characters
-        assert_font_supports_none_of_chars(record.font, sorted(default_emoji))
+            # Check default emoji-style characters
+            assert_font_supports_none_of_chars(record.font, sorted(default_emoji), name)
 
-        # Mark default text-style characters appearing in fonts above the emoji
-        # font as seen
-        if not emoji_font_seen:
-            missing_text_chars -= set(get_best_cmap(record.font))
+            # Mark default text-style characters appearing in fonts above the emoji
+            # font as seen
+            if not emoji_font_seen:
+                missing_text_chars -= set(get_best_cmap(record.font))
 
-    # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and
-    # webdings yet.
-    missing_text_chars -= _chars_by_age['7.0']
-    assert missing_text_chars == set(), (
-        'Text style version of some emoji characters are missing: ' +
-            repr(missing_text_chars))
+        # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and
+        # webdings yet.
+        missing_text_chars -= _chars_by_age['7.0']
+        assert missing_text_chars == set(), (
+            'Text style version of some emoji characters are missing: ' +
+                repr(missing_text_chars))
 
 
 # Setting reverse to true returns a dictionary that maps the values to sets of
@@ -626,8 +658,19 @@
     return all_emoji, default_emoji, equivalent_emoji
 
 
+def check_compact_only_fallback():
+    for name, fallback_chain in _fallback_chains.iteritems():
+        for record in fallback_chain:
+            if record.variant == 'compact':
+                same_script_elegants = [x for x in fallback_chain
+                    if x.scripts == record.scripts and x.variant == 'elegant']
+                assert same_script_elegants, (
+                    '%s must be in elegant of %s as fallback of "%s" too' % (
+                    record.font, record.scripts, record.fallback_for),)
+
+
 def check_vertical_metrics():
-    for record in _fallback_chain:
+    for record in _all_fonts:
         if record.name in ['sans-serif', 'sans-serif-condensed']:
             font = open_font(record.font)
             assert font['head'].yMax == 2163 and font['head'].yMin == -555, (
@@ -646,11 +689,12 @@
 def check_cjk_punctuation():
     cjk_scripts = {'Hans', 'Hant', 'Jpan', 'Kore'}
     cjk_punctuation = range(0x3000, 0x301F + 1)
-    for record in _fallback_chain:
-        if record.scripts.intersection(cjk_scripts):
-            # CJK font seen. Stop checking the rest of the fonts.
-            break
-        assert_font_supports_none_of_chars(record.font, cjk_punctuation)
+    for name, fallback_chain in _fallback_chains.iteritems():
+        for record in fallback_chain:
+            if record.scripts.intersection(cjk_scripts):
+                # CJK font seen. Stop checking the rest of the fonts.
+                break
+            assert_font_supports_none_of_chars(record.font, cjk_punctuation, name)
 
 
 def main():
@@ -661,6 +705,8 @@
     fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml')
     parse_fonts_xml(fonts_xml_path)
 
+    check_compact_only_fallback()
+
     check_vertical_metrics()
 
     hyphens_dir = path.join(target_out, 'usr', 'hyphen-data')
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index c54ab18..cca1294 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -107,6 +107,8 @@
 
     fprintf(out, "namespace android {\n");
     fprintf(out, "namespace util {\n");
+    fprintf(out, "// the single event tag id for all stats logs\n");
+    fprintf(out, "const static int kStatsEventTag = 1937006964;\n");
 
     // Print write methods
     fprintf(out, "\n");
@@ -115,7 +117,7 @@
         int argIndex;
 
         fprintf(out, "void\n");
-        fprintf(out, "stats_write(int code");
+        fprintf(out, "stats_write(int32_t code");
         argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
@@ -126,7 +128,8 @@
 
         fprintf(out, "{\n");
         argIndex = 1;
-        fprintf(out, "    android_log_event_list event(code);\n");
+        fprintf(out, "    android_log_event_list event(kStatsEventTag);\n");
+        fprintf(out, "    event << code;\n");
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
             if (*arg == JAVA_TYPE_STRING) {
@@ -204,8 +207,7 @@
     fprintf(out, "//\n");
     for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
             signature != atoms.signatures.end(); signature++) {
-
-        fprintf(out, "void stats_write(int code");
+        fprintf(out, "void stats_write(int32_t code ");
         int argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
diff --git a/vr/Android.mk b/vr/Android.mk
index 5b65d3f..73e9f23 100644
--- a/vr/Android.mk
+++ b/vr/Android.mk
@@ -18,6 +18,7 @@
 LOCAL_MODULE := libdvr_loader
 LOCAL_MODULE_OWNER := google
 LOCAL_SRC_FILES := dvr_library_loader.cpp
+LOCAL_CFLAGS := -Wall -Werror
 include $(BUILD_SHARED_LIBRARY)
 
 # Java platform library for vr stuff.
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index c2f11d9..a9e1e9d 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,11 +66,11 @@
 
     List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
 
-    int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
+    int addOrUpdateNetwork(in WifiConfiguration config);
 
-    boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
+    boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config);
 
-    boolean removePasspointConfiguration(in String fqdn, String packageName);
+    boolean removePasspointConfiguration(in String fqdn);
 
     List<PasspointConfiguration> getPasspointConfigurations();
 
@@ -80,21 +80,21 @@
 
     void deauthenticateNetwork(long holdoff, boolean ess);
 
-    boolean removeNetwork(int netId, String packageName);
+    boolean removeNetwork(int netId);
 
-    boolean enableNetwork(int netId, boolean disableOthers, String packageName);
+    boolean enableNetwork(int netId, boolean disableOthers);
 
-    boolean disableNetwork(int netId, String packageName);
+    boolean disableNetwork(int netId);
 
-    void startScan(in ScanSettings requested, in WorkSource ws, String packageName);
+    void startScan(in ScanSettings requested, in WorkSource ws, in String packageName);
 
     List<ScanResult> getScanResults(String callingPackage);
 
-    void disconnect(String packageName);
+    void disconnect();
 
-    void reconnect(String packageName);
+    void reconnect();
 
-    void reassociate(String packageName);
+    void reassociate();
 
     WifiInfo getConnectionInfo(String callingPackage);
 
@@ -108,7 +108,7 @@
 
     boolean isDualBandSupported();
 
-    boolean saveConfiguration(String packageName);
+    boolean saveConfiguration();
 
     DhcpInfo getDhcpInfo();
 
@@ -134,9 +134,9 @@
 
     boolean stopSoftAp();
 
-    int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, String packageName);
+    int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, in String packageName);
 
-    void stopLocalOnlyHotspot(String packageName);
+    void stopLocalOnlyHotspot();
 
     void startWatchLocalOnlyHotspot(in Messenger messenger, in IBinder binder);
 
@@ -146,9 +146,9 @@
 
     WifiConfiguration getWifiApConfiguration();
 
-    void setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName);
+    void setWifiApConfiguration(in WifiConfiguration wifiConfig);
 
-    Messenger getWifiServiceMessenger(String packageName);
+    Messenger getWifiServiceMessenger();
 
     void enableTdls(String remoteIPAddress, boolean enable);
 
@@ -166,16 +166,16 @@
     void setAllowScansWithTraffic(int enabled);
     int getAllowScansWithTraffic();
 
-    boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName);
+    boolean setEnableAutoJoinWhenAssociated(boolean enabled);
     boolean getEnableAutoJoinWhenAssociated();
 
     void enableWifiConnectivityManager(boolean enabled);
 
     WifiConnectionStatistics getConnectionStatistics();
 
-    void disableEphemeralNetwork(String SSID, String packageName);
+    void disableEphemeralNetwork(String SSID);
 
-    void factoryReset(String packageName);
+    void factoryReset();
 
     Network getCurrentNetwork();
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 66fabf3..183cf0d 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1128,7 +1128,7 @@
      */
     private int addOrUpdateNetwork(WifiConfiguration config) {
         try {
-            return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
+            return mService.addOrUpdateNetwork(config);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1149,7 +1149,7 @@
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
-            if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
+            if (!mService.addOrUpdatePasspointConfiguration(config)) {
                 throw new IllegalArgumentException();
             }
         } catch (RemoteException e) {
@@ -1166,7 +1166,7 @@
      */
     public void removePasspointConfiguration(String fqdn) {
         try {
-            if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
+            if (!mService.removePasspointConfiguration(fqdn)) {
                 throw new IllegalArgumentException();
             }
         } catch (RemoteException e) {
@@ -1252,7 +1252,7 @@
      */
     public boolean removeNetwork(int netId) {
         try {
-            return mService.removeNetwork(netId, mContext.getOpPackageName());
+            return mService.removeNetwork(netId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1298,7 +1298,7 @@
 
         boolean success;
         try {
-            success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
+            success = mService.enableNetwork(netId, attemptConnect);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1324,7 +1324,7 @@
      */
     public boolean disableNetwork(int netId) {
         try {
-            return mService.disableNetwork(netId, mContext.getOpPackageName());
+            return mService.disableNetwork(netId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1337,7 +1337,7 @@
      */
     public boolean disconnect() {
         try {
-            mService.disconnect(mContext.getOpPackageName());
+            mService.disconnect();
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1352,7 +1352,7 @@
      */
     public boolean reconnect() {
         try {
-            mService.reconnect(mContext.getOpPackageName());
+            mService.reconnect();
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1367,7 +1367,7 @@
      */
     public boolean reassociate() {
         try {
-            mService.reassociate(mContext.getOpPackageName());
+            mService.reassociate();
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1740,7 +1740,7 @@
     @Deprecated
     public boolean saveConfiguration() {
         try {
-            return mService.saveConfiguration(mContext.getOpPackageName());
+            return mService.saveConfiguration();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2056,7 +2056,7 @@
             }
             mLOHSCallbackProxy = null;
             try {
-                mService.stopLocalOnlyHotspot(mContext.getOpPackageName());
+                mService.stopLocalOnlyHotspot();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -2175,7 +2175,7 @@
     @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
     public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
         try {
-            mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
+            mService.setWifiApConfiguration(wifiConfig);
             return true;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -2947,7 +2947,7 @@
     public void disableEphemeralNetwork(String SSID) {
         if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
         try {
-            mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
+            mService.disableEphemeralNetwork(SSID);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2986,7 +2986,7 @@
      */
     public Messenger getWifiServiceMessenger() {
         try {
-            return mService.getWifiServiceMessenger(mContext.getOpPackageName());
+            return mService.getWifiServiceMessenger();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3516,7 +3516,7 @@
      */
     public void factoryReset() {
         try {
-            mService.factoryReset(mContext.getOpPackageName());
+            mService.factoryReset();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3543,7 +3543,7 @@
      */
     public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
         try {
-            return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName());
+            return mService.setEnableAutoJoinWhenAssociated(enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
index 115b86d..aa2c268 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -113,6 +113,32 @@
     }
 
     /**
+     * Called when a discovery (publish or subscribe) operation results in a
+     * service discovery. Called when a Subscribe service was configured with a range requirement
+     * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and/or
+     * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)}. A discovery will only be declared
+     * (i.e. this callback called) if the range of the publisher is within the specified distance
+     * constraints.
+     *
+     * @param peerHandle An opaque handle to the peer matching our discovery operation.
+     * @param serviceSpecificInfo The service specific information (arbitrary
+     *            byte array) provided by the peer as part of its discovery
+     *            configuration.
+     * @param matchFilter The filter which resulted in this service discovery. For
+     * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED},
+     * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE} discovery sessions this is the publisher's
+     *                    match filter. For {@link PublishConfig#PUBLISH_TYPE_SOLICITED},
+     *                    {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this
+     *                    is the subscriber's match filter.
+     * @param distanceMm The measured distance to the Publisher in mm.
+     * @hide
+     */
+    public void onServiceDiscoveredWithinRange(PeerHandle peerHandle,
+        byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm) {
+        /* empty */
+    }
+
+    /**
      * Called in response to
      * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}
      * when a message is transmitted successfully - i.e. when it was received successfully by the
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl
index 8ff3842..421a8af 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl
@@ -29,6 +29,8 @@
     void onSessionTerminated(int reason);
 
     void onMatch(int peerId, in byte[] serviceSpecificInfo, in byte[] matchFilter);
+    void onMatchWithDistance(int peerId, in byte[] serviceSpecificInfo, in byte[] matchFilter,
+            int distanceMm);
 
     void onMessageSendSuccess(int messageId);
     void onMessageSendFail(int messageId, int reason);
diff --git a/wifi/java/android/net/wifi/aware/PublishConfig.java b/wifi/java/android/net/wifi/aware/PublishConfig.java
index d018620..e60f52f 100644
--- a/wifi/java/android/net/wifi/aware/PublishConfig.java
+++ b/wifi/java/android/net/wifi/aware/PublishConfig.java
@@ -29,6 +29,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Defines the configuration of a Aware publish session. Built using
@@ -81,14 +82,19 @@
     public final boolean mEnableTerminateNotification;
 
     /** @hide */
+    public final boolean mEnableRanging;
+
+    /** @hide */
     public PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
-            int publishType, int ttlSec, boolean enableTerminateNotification) {
+            int publishType, int ttlSec, boolean enableTerminateNotification,
+            boolean enableRanging) {
         mServiceName = serviceName;
         mServiceSpecificInfo = serviceSpecificInfo;
         mMatchFilter = matchFilter;
         mPublishType = publishType;
         mTtlSec = ttlSec;
         mEnableTerminateNotification = enableTerminateNotification;
+        mEnableRanging = enableRanging;
     }
 
     @Override
@@ -103,7 +109,8 @@
                 + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
                 + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
                 + ", mPublishType=" + mPublishType + ", mTtlSec=" + mTtlSec
-                + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
+                + ", mEnableTerminateNotification=" + mEnableTerminateNotification
+                + ", mEnableRanging=" + mEnableRanging + "]";
     }
 
     @Override
@@ -119,6 +126,7 @@
         dest.writeInt(mPublishType);
         dest.writeInt(mTtlSec);
         dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+        dest.writeInt(mEnableRanging ? 1 : 0);
     }
 
     public static final Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() {
@@ -135,9 +143,10 @@
             int publishType = in.readInt();
             int ttlSec = in.readInt();
             boolean enableTerminateNotification = in.readInt() != 0;
+            boolean enableRanging = in.readInt() != 0;
 
             return new PublishConfig(serviceName, ssi, matchFilter, publishType,
-                    ttlSec, enableTerminateNotification);
+                    ttlSec, enableTerminateNotification, enableRanging);
         }
     };
 
@@ -157,21 +166,14 @@
                 lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
                 && mPublishType == lhs.mPublishType
                 && mTtlSec == lhs.mTtlSec
-                && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+                && mEnableTerminateNotification == lhs.mEnableTerminateNotification
+                && mEnableRanging == lhs.mEnableRanging;
     }
 
     @Override
     public int hashCode() {
-        int result = 17;
-
-        result = 31 * result + Arrays.hashCode(mServiceName);
-        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
-        result = 31 * result + Arrays.hashCode(mMatchFilter);
-        result = 31 * result + mPublishType;
-        result = 31 * result + mTtlSec;
-        result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
-
-        return result;
+        return Objects.hash(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, mTtlSec,
+                mEnableTerminateNotification, mEnableRanging);
     }
 
     /**
@@ -226,6 +228,7 @@
         private int mPublishType = PUBLISH_TYPE_UNSOLICITED;
         private int mTtlSec = 0;
         private boolean mEnableTerminateNotification = true;
+        private boolean mEnableRanging = false;
 
         /**
          * Specify the service name of the publish session. The actual on-air
@@ -352,12 +355,35 @@
         }
 
         /**
+         * Configure whether the publish discovery session supports ranging and allows peers to
+         * measure distance to it. This API is used in conjunction with
+         * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and
+         * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} to specify a minimum and/or
+         * maximum distance at which discovery will be triggered.
+         * <p>
+         * Optional. Disabled by default - i.e. any peer which attempts to measure distance to this
+         * device will be refused. If the peer has ranging enabled (using the
+         * {@link SubscribeConfig} APIs listed above, it will never discover this device.
+         *
+         * @param enable If true, ranging is supported on request of the peer.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         *
+         * @hide
+         */
+        public Builder setRangingEnabled(boolean enable) {
+            mEnableRanging = enable;
+            return this;
+        }
+
+        /**
          * Build {@link PublishConfig} given the current requests made on the
          * builder.
          */
         public PublishConfig build() {
             return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType,
-                    mTtlSec, mEnableTerminateNotification);
+                    mTtlSec, mEnableTerminateNotification, mEnableRanging);
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.java b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
index 4bf2fb6..f6552a7 100644
--- a/wifi/java/android/net/wifi/aware/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
@@ -29,6 +29,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Defines the configuration of a Aware subscribe session. Built using
@@ -79,15 +80,32 @@
     public final boolean mEnableTerminateNotification;
 
     /** @hide */
+    public final boolean mMinDistanceMmSet;
+
+    /** @hide */
+    public final int mMinDistanceMm;
+
+    /** @hide */
+    public final boolean mMaxDistanceMmSet;
+
+    /** @hide */
+    public final int mMaxDistanceMm;
+
+    /** @hide */
     public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
-            int subscribeType, int ttlSec,
-            boolean enableTerminateNotification) {
+            int subscribeType, int ttlSec, boolean enableTerminateNotification,
+            boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet,
+            int maxDistanceMm) {
         mServiceName = serviceName;
         mServiceSpecificInfo = serviceSpecificInfo;
         mMatchFilter = matchFilter;
         mSubscribeType = subscribeType;
         mTtlSec = ttlSec;
         mEnableTerminateNotification = enableTerminateNotification;
+        mMinDistanceMm = minDistanceMm;
+        mMinDistanceMmSet = minDistanceMmSet;
+        mMaxDistanceMm = maxDistanceMm;
+        mMaxDistanceMmSet = maxDistanceMmSet;
     }
 
     @Override
@@ -102,7 +120,11 @@
                 + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
                 + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
                 + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
-                + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
+                + ", mEnableTerminateNotification=" + mEnableTerminateNotification
+                + ", mMinDistanceMm=" + mMinDistanceMm
+                + ", mMinDistanceMmSet=" + mMinDistanceMmSet
+                + ", mMaxDistanceMm=" + mMaxDistanceMm
+                + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]";
     }
 
     @Override
@@ -118,6 +140,10 @@
         dest.writeInt(mSubscribeType);
         dest.writeInt(mTtlSec);
         dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+        dest.writeInt(mMinDistanceMm);
+        dest.writeInt(mMinDistanceMmSet ? 1 : 0);
+        dest.writeInt(mMaxDistanceMm);
+        dest.writeInt(mMaxDistanceMmSet ? 1 : 0);
     }
 
     public static final Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() {
@@ -134,9 +160,14 @@
             int subscribeType = in.readInt();
             int ttlSec = in.readInt();
             boolean enableTerminateNotification = in.readInt() != 0;
+            int minDistanceMm = in.readInt();
+            boolean minDistanceMmSet = in.readInt() != 0;
+            int maxDistanceMm = in.readInt();
+            boolean maxDistanceMmSet = in.readInt() != 0;
 
-            return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType,
-                    ttlSec, enableTerminateNotification);
+            return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec,
+                    enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet,
+                    maxDistanceMm);
         }
     };
 
@@ -152,23 +183,37 @@
 
         SubscribeConfig lhs = (SubscribeConfig) o;
 
-        return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo,
-                lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
-                && mSubscribeType == lhs.mSubscribeType
-                && mTtlSec == lhs.mTtlSec
-                && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+        if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(
+                mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter,
+                lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec
+                && mEnableTerminateNotification == lhs.mEnableTerminateNotification
+                && mMinDistanceMmSet == lhs.mMinDistanceMmSet
+                && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet)) {
+            return false;
+        }
+
+        if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) {
+            return false;
+        }
+
+        if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) {
+            return false;
+        }
+
+        return true;
     }
 
     @Override
     public int hashCode() {
-        int result = 17;
+        int result = Objects.hash(mServiceName, mServiceSpecificInfo, mMatchFilter, mSubscribeType,
+                mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet, mMaxDistanceMmSet);
 
-        result = 31 * result + Arrays.hashCode(mServiceName);
-        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
-        result = 31 * result + Arrays.hashCode(mMatchFilter);
-        result = 31 * result + mSubscribeType;
-        result = 31 * result + mTtlSec;
-        result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
+        if (mMinDistanceMmSet) {
+            result = Objects.hash(result, mMinDistanceMm);
+        }
+        if (mMaxDistanceMmSet) {
+            result = Objects.hash(result, mMaxDistanceMm);
+        }
 
         return result;
     }
@@ -213,6 +258,17 @@
                         "Match filter longer than supported by device characteristics");
             }
         }
+
+        if (mMinDistanceMmSet && mMinDistanceMm < 0) {
+            throw new IllegalArgumentException("Minimum distance must be non-negative");
+        }
+        if (mMaxDistanceMmSet && mMaxDistanceMm < 0) {
+            throw new IllegalArgumentException("Maximum distance must be non-negative");
+        }
+        if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) {
+            throw new IllegalArgumentException(
+                    "Maximum distance must be greater than minimum distance");
+        }
     }
 
     /**
@@ -225,6 +281,10 @@
         private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
         private int mTtlSec = 0;
         private boolean mEnableTerminateNotification = true;
+        private boolean mMinDistanceMmSet = false;
+        private int mMinDistanceMm;
+        private boolean mMaxDistanceMmSet = false;
+        private int mMaxDistanceMm;
 
         /**
          * Specify the service name of the subscribe session. The actual on-air
@@ -350,13 +410,69 @@
         }
 
         /**
+         * Configure the minimum distance to a discovered publisher at which to trigger a discovery
+         * notification. I.e. discovery will only be triggered if we've found a matching publisher
+         * (based on the other criteria in this configuration) <b>and</b> the distance to the
+         * publisher is > the value specified in this API.
+         * <p>
+         * Can be used in conjunction with {@link #setMaxDistanceMm(int)} to specify a geo-fence,
+         * i.e. discovery with min < distance < max.
+         * <p>
+         * If this API is called, the subscriber requires ranging. In such a case, the publisher
+         * peer must enable ranging using
+         * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. Otherwise discovery will
+         * never be triggered.
+         *
+         * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger
+         *                      discovery.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         *
+         * @hide
+         */
+        public Builder setMinDistanceMm(int minDistanceMm) {
+            mMinDistanceMm = minDistanceMm;
+            mMinDistanceMmSet = true;
+            return this;
+        }
+
+        /**
+         * Configure the maximum distance to a discovered publisher at which to trigger a discovery
+         * notification. I.e. discovery will only be triggered if we've found a matching publisher
+         * (based on the other criteria in this configuration) <b>and</b> the distance to the
+         * publisher is < the value specified in this API.
+         * <p>
+         * Can be used in conjunction with {@link #setMinDistanceMm(int)} to specify a geo-fence,
+         * i.e. discovery with min < distance < max.
+         * <p>
+         * If this API is called, the subscriber requires ranging. In such a case, the publisher
+         * peer must enable ranging using
+         * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. Otherwise discovery will
+         * never be triggered.
+         *
+         * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger
+         *                      discovery.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         *
+         * @hide
+         */
+        public Builder setMaxDistanceMm(int maxDistanceMm) {
+            mMaxDistanceMm = maxDistanceMm;
+            mMaxDistanceMmSet = true;
+            return this;
+        }
+
+        /**
          * Build {@link SubscribeConfig} given the current requests made on the
          * builder.
          */
         public SubscribeConfig build() {
             return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
-                    mSubscribeType, mTtlSec,
-                    mEnableTerminateNotification);
+                    mSubscribeType, mTtlSec, mEnableTerminateNotification,
+                    mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm);
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index ed6804d..166da48 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -20,8 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
@@ -564,6 +564,7 @@
         private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5;
         private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
         private static final int CALLBACK_MESSAGE_RECEIVED = 7;
+        private static final int CALLBACK_MATCH_WITH_DISTANCE = 8;
 
         private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
         private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
@@ -618,7 +619,9 @@
                         case CALLBACK_SESSION_TERMINATED:
                             onProxySessionTerminated(msg.arg1);
                             break;
-                        case CALLBACK_MATCH: {
+                        case CALLBACK_MATCH:
+                        case CALLBACK_MATCH_WITH_DISTANCE:
+                            {
                             List<byte[]> matchFilter = null;
                             byte[] arg = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2);
                             try {
@@ -629,9 +632,16 @@
                                         + new String(HexEncoding.encode(arg))
                                         + "' - cannot be parsed: e=" + e);
                             }
-                            mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
-                                    msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
-                                    matchFilter);
+                            if (msg.what == CALLBACK_MATCH) {
+                                mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
+                                        msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+                                        matchFilter);
+                            } else {
+                                mOriginalCallback.onServiceDiscoveredWithinRange(
+                                        new PeerHandle(msg.arg1),
+                                        msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+                                        matchFilter, msg.arg2);
+                            }
                             break;
                         }
                         case CALLBACK_MESSAGE_SEND_SUCCESS:
@@ -684,21 +694,38 @@
             mHandler.sendMessage(msg);
         }
 
-        @Override
-        public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
-            if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
-
+        private void onMatchCommon(int messageType, int peerId, byte[] serviceSpecificInfo,
+                byte[] matchFilter, int distanceMm) {
             Bundle data = new Bundle();
             data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
             data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
 
-            Message msg = mHandler.obtainMessage(CALLBACK_MATCH);
+            Message msg = mHandler.obtainMessage(messageType);
             msg.arg1 = peerId;
+            msg.arg2 = distanceMm;
             msg.setData(data);
             mHandler.sendMessage(msg);
         }
 
         @Override
+        public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+            if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
+
+            onMatchCommon(CALLBACK_MATCH, peerId, serviceSpecificInfo, matchFilter, 0);
+        }
+
+        @Override
+        public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
+                int distanceMm) {
+            if (VDBG) {
+                Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm);
+            }
+
+            onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter,
+                    distanceMm);
+        }
+
+        @Override
         public void onMessageSendSuccess(int messageId) {
             if (VDBG) Log.v(TAG, "onMessageSendSuccess");
 
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
index 128d6c9..735e872 100644
--- a/wifi/java/android/net/wifi/rtt/WifiRttManager.java
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -44,7 +44,7 @@
 @SystemService(Context.WIFI_RTT2_SERVICE)
 public class WifiRttManager {
     private static final String TAG = "WifiRttManager";
-    private static final boolean VDBG = true;
+    private static final boolean VDBG = false;
 
     private final Context mContext;
     private final IWifiRttManager mService;
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 3cad590..0df5615 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -137,7 +137,7 @@
 
         assertEquals(mApConfig, callback.mRes.getWifiConfiguration());
         callback.mRes.close();
-        verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+        verify(mWifiService).stopLocalOnlyHotspot();
     }
 
     /**
@@ -157,7 +157,7 @@
             assertEquals(mApConfig, res.getWifiConfiguration());
         }
 
-        verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+        verify(mWifiService).stopLocalOnlyHotspot();
     }
 
     /**
@@ -548,7 +548,7 @@
                 anyString())).thenReturn(REQUEST_REGISTERED);
         mWifiManager.startLocalOnlyHotspot(callback, mHandler);
         mWifiManager.cancelLocalOnlyHotspotRequest();
-        verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+        verify(mWifiService).stopLocalOnlyHotspot();
     }
 
     /**
@@ -570,7 +570,7 @@
                 anyString())).thenReturn(REQUEST_REGISTERED);
         mWifiManager.startLocalOnlyHotspot(callback, mHandler);
         mWifiManager.cancelLocalOnlyHotspotRequest();
-        verify(mWifiService).stopLocalOnlyHotspot(TEST_PACKAGE_NAME);
+        verify(mWifiService).stopLocalOnlyHotspot();
         mLooper.dispatchAll();
         assertEquals(ERROR_NOT_SET, callback.mFailureReason);
         assertFalse(callback.mOnStartedCalled);
@@ -594,7 +594,7 @@
         assertFalse(callback.mOnStoppedCalled);
         assertEquals(null, callback.mRes);
         mWifiManager.cancelLocalOnlyHotspotRequest();
-        verify(mWifiService, never()).stopLocalOnlyHotspot(anyString());
+        verify(mWifiService, never()).stopLocalOnlyHotspot();
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 1aeeee3..653fcff 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -19,20 +19,17 @@
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.wifi.RttManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
-import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -407,6 +404,7 @@
         final byte[] matchFilter = { 1, 12, 3, 31, 32 }; // bad data!
         final int messageId = 2123;
         final int reason = AWARE_STATUS_ERROR;
+        final int distanceMm = 100;
 
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
                 mockSubscribeSession);
@@ -442,6 +440,8 @@
         // (3) ...
         subscribeSession.getValue().sendMessage(peerHandle, messageId, string1.getBytes());
         sessionProxyCallback.getValue().onMatch(peerHandle.peerId, string1.getBytes(), matchFilter);
+        sessionProxyCallback.getValue().onMatchWithDistance(peerHandle.peerId, string1.getBytes(),
+                matchFilter, distanceMm);
         sessionProxyCallback.getValue().onMessageReceived(peerHandle.peerId, string1.getBytes());
         sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
         sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
@@ -450,7 +450,9 @@
         inOrder.verify(mockAwareService).sendMessage(eq(clientId), eq(sessionId),
                 eq(peerHandle.peerId), eq(string1.getBytes()), eq(messageId), eq(0));
         inOrder.verify(mockSessionCallback).onServiceDiscovered(peerIdCaptor.capture(),
-                eq(string1.getBytes()), (List<byte[]>) isNull());
+                eq(string1.getBytes()), isNull());
+        inOrder.verify(mockSessionCallback).onServiceDiscoveredWithinRange(peerIdCaptor.capture(),
+                eq(string1.getBytes()), isNull(), eq(distanceMm));
         assertEquals((peerIdCaptor.getValue()).peerId, peerHandle.peerId);
         inOrder.verify(mockSessionCallback).onMessageReceived(peerIdCaptor.capture(),
                 eq(string1.getBytes()));
@@ -685,11 +687,18 @@
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
 
         collector.checkThat("mServiceName", subscribeConfig.mServiceName, equalTo(null));
+        collector.checkThat("mServiceSpecificInfo", subscribeConfig.mServiceSpecificInfo,
+                equalTo(null));
+        collector.checkThat("mMatchFilter", subscribeConfig.mMatchFilter, equalTo(null));
         collector.checkThat("mSubscribeType", subscribeConfig.mSubscribeType,
                 equalTo(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE));
         collector.checkThat("mTtlSec", subscribeConfig.mTtlSec, equalTo(0));
         collector.checkThat("mEnableTerminateNotification",
                 subscribeConfig.mEnableTerminateNotification, equalTo(true));
+        collector.checkThat("mMinDistanceCmSet", subscribeConfig.mMinDistanceMmSet, equalTo(false));
+        collector.checkThat("mMinDistanceMm", subscribeConfig.mMinDistanceMm, equalTo(0));
+        collector.checkThat("mMaxDistanceMmSet", subscribeConfig.mMaxDistanceMmSet, equalTo(false));
+        collector.checkThat("mMaxDistanceMm", subscribeConfig.mMaxDistanceMm, equalTo(0));
     }
 
     @Test
@@ -698,16 +707,19 @@
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] matchFilter = { 1, 16, 1, 22 };
         final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
-        final int subscribeCount = 10;
         final int subscribeTtl = 15;
         final boolean enableTerminateNotification = false;
+        final int minDistance = 10;
+        final int maxDistance = 50;
 
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(
-                        new TlvBufferUtils.TlvIterable(0, 1, matchFilter).toList())
+                    new TlvBufferUtils.TlvIterable(0, 1, matchFilter).toList())
                 .setSubscribeType(subscribeType)
                 .setTtlSec(subscribeTtl)
-                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+                .setTerminateNotificationEnabled(enableTerminateNotification)
+                .setMinDistanceMm(minDistance)
+                .setMaxDistanceMm(maxDistance).build();
 
         collector.checkThat("mServiceName", serviceName.getBytes(),
                 equalTo(subscribeConfig.mServiceName));
@@ -719,6 +731,10 @@
         collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeConfig.mTtlSec));
         collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
                 equalTo(subscribeConfig.mEnableTerminateNotification));
+        collector.checkThat("mMinDistanceMmSet", true, equalTo(subscribeConfig.mMinDistanceMmSet));
+        collector.checkThat("mMinDistanceMm", minDistance, equalTo(subscribeConfig.mMinDistanceMm));
+        collector.checkThat("mMaxDistanceMmSet", true, equalTo(subscribeConfig.mMaxDistanceMmSet));
+        collector.checkThat("mMaxDistanceMm", maxDistance, equalTo(subscribeConfig.mMaxDistanceMm));
     }
 
     @Test
@@ -729,13 +745,17 @@
         final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
         final int subscribeTtl = 15;
         final boolean enableTerminateNotification = true;
+        final int minDistance = 10;
+        final int maxDistance = 50;
 
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(
                         new TlvBufferUtils.TlvIterable(0, 1, matchFilter).toList())
                 .setSubscribeType(subscribeType)
                 .setTtlSec(subscribeTtl)
-                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+                .setTerminateNotificationEnabled(enableTerminateNotification)
+                .setMinDistanceMm(minDistance)
+                .setMaxDistanceMm(maxDistance).build();
 
         Parcel parcelW = Parcel.obtain();
         subscribeConfig.writeToParcel(parcelW, 0);
@@ -769,11 +789,15 @@
         PublishConfig publishConfig = new PublishConfig.Builder().build();
 
         collector.checkThat("mServiceName", publishConfig.mServiceName, equalTo(null));
+        collector.checkThat("mServiceSpecificInfo", publishConfig.mServiceSpecificInfo,
+                equalTo(null));
+        collector.checkThat("mMatchFilter", publishConfig.mMatchFilter, equalTo(null));
         collector.checkThat("mPublishType", publishConfig.mPublishType,
                 equalTo(PublishConfig.PUBLISH_TYPE_UNSOLICITED));
         collector.checkThat("mTtlSec", publishConfig.mTtlSec, equalTo(0));
         collector.checkThat("mEnableTerminateNotification",
                 publishConfig.mEnableTerminateNotification, equalTo(true));
+        collector.checkThat("mEnableRanging", publishConfig.mEnableRanging, equalTo(false));
     }
 
     @Test
@@ -782,16 +806,17 @@
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] matchFilter = { 1, 16, 1, 22 };
         final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
         final int publishTtl = 15;
         final boolean enableTerminateNotification = false;
+        final boolean enableRanging = true;
 
         PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(
                         new TlvBufferUtils.TlvIterable(0, 1, matchFilter).toList())
                 .setPublishType(publishType)
                 .setTtlSec(publishTtl)
-                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+                .setTerminateNotificationEnabled(enableTerminateNotification)
+                .setRangingEnabled(enableRanging).build();
 
         collector.checkThat("mServiceName", serviceName.getBytes(),
                 equalTo(publishConfig.mServiceName));
@@ -802,6 +827,7 @@
         collector.checkThat("mTtlSec", publishTtl, equalTo(publishConfig.mTtlSec));
         collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
                 equalTo(publishConfig.mEnableTerminateNotification));
+        collector.checkThat("mEnableRanging", enableRanging, equalTo(publishConfig.mEnableRanging));
     }
 
     @Test
@@ -810,16 +836,17 @@
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] matchFilter = { 1, 16, 1, 22 };
         final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
         final int publishTtl = 15;
         final boolean enableTerminateNotification = false;
+        final boolean enableRanging = true;
 
         PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(
                         new TlvBufferUtils.TlvIterable(0, 1, matchFilter).toList())
                 .setPublishType(publishType)
                 .setTtlSec(publishTtl)
-                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+                .setTerminateNotificationEnabled(enableTerminateNotification)
+                .setRangingEnabled(enableRanging).build();
 
         Parcel parcelW = Parcel.obtain();
         publishConfig.writeToParcel(parcelW, 0);