Merge "Fixed auto-brightness first screen update."
diff --git a/Android.bp b/Android.bp
index db0f0ea..694bf26 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
 
 java_library {
     name: "framework",
+    installable: true,
 
     srcs: [
         // From build/make/core/pathmap.mk FRAMEWORK_BASE_SUBDIRS
@@ -706,6 +707,7 @@
 // specified on the build command line.
 java_library {
     name: "framework-oahl-backward-compatibility",
+    installable: true,
     srcs: [
         "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
     ],
@@ -761,6 +763,7 @@
 // ============================================================
 java_library {
     name: "ext",
+    installable: true,
     no_framework_libs: true,
     static_libs: [
         "libphonenumber-platform",
@@ -1192,6 +1195,24 @@
           " -showAnnotation android.annotation.TestApi",
 }
 
+droiddoc {
+    name: "hiddenapi-mappings",
+    defaults: ["framework-docs-default"],
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+        ":api-version-xml",
+        "core/java/overview.html",
+        ":current-support-api",
+    ],
+    dex_mapping_filename: "dex-mapping.txt",
+    args: framework_docs_args +
+          " -referenceonly" +
+          " -nodocs" +
+          " -showUnannotated" +
+          " -showAnnotation android.annotation.SystemApi" +
+          " -showAnnotation android.annotation.TestApi",
+}
+
 filegroup {
     name: "apache-http-stubs-sources",
     srcs: [
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
index 586c385..64f2800 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -46,7 +46,7 @@
     private static final float SPACING_ADD = 10f;
     private static final float SPACING_MULT = 1.5f;
 
-    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+    @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
index 9d11f29..194a88c 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
@@ -40,7 +40,7 @@
 
     private static final boolean[] BOOLEANS = new boolean[]{false, true};
 
-    @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
+    @Parameterized.Parameters(name = "cached {4} {1}chars {0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
index 6768798..ad5a34e 100644
--- a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
@@ -42,7 +42,7 @@
 
     private static final boolean[] BOOLEANS = new boolean[]{false, true};
 
-    @Parameterized.Parameters(name = "cached={1},{0}chars")
+    @Parameterized.Parameters(name = "cached {1} {0}chars")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
index bfdb758..deb2b0a 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -50,7 +50,7 @@
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+    @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
index ff2d57e..c2898fa 100644
--- a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
@@ -51,7 +51,7 @@
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+    @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
     public static Collection cases() {
         final List<Object[]> params = new ArrayList<>();
         for (int length : new int[]{128}) {
diff --git a/api/current.txt b/api/current.txt
index dcc19e6..e471086 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9137,6 +9137,7 @@
     method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
     method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
     method public void close();
+    method public static void closeQuietly(android.content.ContentProviderClient);
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
     method public android.content.ContentProvider getLocalContentProvider();
     method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -32547,6 +32548,21 @@
     ctor public FileUriExposedException(java.lang.String);
   }
 
+  public class FileUtils {
+    method public static void closeQuietly(java.lang.AutoCloseable);
+    method public static void closeQuietly(java.io.FileDescriptor);
+    method public static long copy(java.io.File, java.io.File) throws java.io.IOException;
+    method public static long copy(java.io.File, java.io.File, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+    method public static long copy(java.io.InputStream, java.io.OutputStream) throws java.io.IOException;
+    method public static long copy(java.io.InputStream, java.io.OutputStream, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+    method public static long copy(java.io.FileDescriptor, java.io.FileDescriptor) throws java.io.IOException;
+    method public static long copy(java.io.FileDescriptor, java.io.FileDescriptor, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+  }
+
+  public static abstract interface FileUtils.ProgressListener {
+    method public abstract void onProgress(long);
+  }
+
   public class Handler {
     ctor public Handler();
     ctor public Handler(android.os.Handler.Callback);
@@ -42510,13 +42526,11 @@
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
     method public java.lang.String getImei();
     method public java.lang.String getImei(int);
-    method public java.lang.String getTypeAllocationCode();
-    method public java.lang.String getTypeAllocationCode(int);
     method public java.lang.String getLine1Number();
-    method public java.lang.String getMeid();
-    method public java.lang.String getMeid(int);
     method public java.lang.String getManufacturerCode();
     method public java.lang.String getManufacturerCode(int);
+    method public java.lang.String getMeid();
+    method public java.lang.String getMeid(int);
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
     method public java.lang.String getNai();
@@ -42539,6 +42553,8 @@
     method public int getSimState();
     method public int getSimState(int);
     method public java.lang.String getSubscriberId();
+    method public java.lang.String getTypeAllocationCode();
+    method public java.lang.String getTypeAllocationCode(int);
     method public java.lang.String getVisualVoicemailPackageName();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
@@ -44751,6 +44767,7 @@
   public class Linkify {
     ctor public Linkify();
     method public static final boolean addLinks(android.text.Spannable, int);
+    method public static final boolean addLinks(android.text.Spannable, int, android.text.util.Linkify.UrlSpanFactory);
     method public static final boolean addLinks(android.widget.TextView, int);
     method public static final void addLinks(android.widget.TextView, java.util.regex.Pattern, java.lang.String);
     method public static final void addLinks(android.widget.TextView, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
@@ -44758,6 +44775,7 @@
     method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String);
     method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
     method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
+    method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter, android.text.util.Linkify.UrlSpanFactory);
     field public static final int ALL = 15; // 0xf
     field public static final int EMAIL_ADDRESSES = 2; // 0x2
     field public static final deprecated int MAP_ADDRESSES = 8; // 0x8
@@ -44776,6 +44794,11 @@
     method public abstract java.lang.String transformUrl(java.util.regex.Matcher, java.lang.String);
   }
 
+  public static class Linkify.UrlSpanFactory {
+    ctor public Linkify.UrlSpanFactory();
+    method public android.text.style.URLSpan create(java.lang.String);
+  }
+
   public class Rfc822Token {
     ctor public Rfc822Token(java.lang.String, java.lang.String, java.lang.String);
     method public java.lang.String getAddress();
@@ -49397,18 +49420,18 @@
     method public void setCheckable(boolean);
     method public void setChecked(boolean);
     method public void setClassName(java.lang.CharSequence);
-    method public void setClickable(boolean);
+    method public deprecated void setClickable(boolean);
     method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
     method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
     method public void setContentDescription(java.lang.CharSequence);
     method public void setContentInvalid(boolean);
-    method public void setContextClickable(boolean);
-    method public void setDismissable(boolean);
+    method public deprecated void setContextClickable(boolean);
+    method public deprecated void setDismissable(boolean);
     method public void setDrawingOrder(int);
     method public void setEditable(boolean);
     method public void setEnabled(boolean);
     method public void setError(java.lang.CharSequence);
-    method public void setFocusable(boolean);
+    method public deprecated void setFocusable(boolean);
     method public void setFocused(boolean);
     method public void setHeading(boolean);
     method public void setHintText(java.lang.CharSequence);
@@ -49419,7 +49442,7 @@
     method public void setLabeledBy(android.view.View);
     method public void setLabeledBy(android.view.View, int);
     method public void setLiveRegion(int);
-    method public void setLongClickable(boolean);
+    method public deprecated void setLongClickable(boolean);
     method public void setMaxTextLength(int);
     method public void setMovementGranularities(int);
     method public void setMultiLine(boolean);
@@ -49430,7 +49453,7 @@
     method public void setPassword(boolean);
     method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo);
     method public void setScreenReaderFocusable(boolean);
-    method public void setScrollable(boolean);
+    method public deprecated void setScrollable(boolean);
     method public void setSelected(boolean);
     method public void setShowingHintText(boolean);
     method public void setSource(android.view.View);
diff --git a/api/system-current.txt b/api/system-current.txt
index 049a9d2..7c0d958 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4664,6 +4664,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final java.lang.String KEY_PEOPLE = "key_people";
+    field public static final java.lang.String KEY_SMART_ACTIONS = "key_smart_actions";
     field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index e5061ed..b8acfdb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1043,6 +1043,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final java.lang.String KEY_PEOPLE = "key_people";
+    field public static final java.lang.String KEY_SMART_ACTIONS = "key_smart_actions";
     field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index cdb72ab..e23676f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -126,7 +126,7 @@
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10022
+    // Next: 10023
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -150,6 +150,7 @@
         RemainingBatteryCapacity remaining_battery_capacity = 10019;
         FullBatteryCapacity full_battery_capacity = 10020;
         Temperature temperature = 10021;
+        BinderCalls binder_calls = 10022;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP. Field numbers above
@@ -1975,3 +1976,39 @@
     // Temperature in tenths of a degree C.
     optional int32 temperature_dC = 3;
 }
+
+/**
+ * Pulls the statistics of calls to Binder.
+ *
+ * Binder stats are cumulative from boot unless somebody reset the data using
+ * > adb shell dumpsys binder_calls_stats --reset
+ */
+message BinderCalls {
+   // TODO(gaillard): figure out if binder call stats includes data from isolated uids, if a uid
+   // gets recycled and we have isolated uids, we might attribute the data incorrectly.
+   // TODO(gaillard): there is a high dimensions cardinality, figure out if we should drop the less
+   // commonly used APIs.
+   optional int32 uid = 1 [(is_uid) = true];
+   // Fully qualified class name of the API call.
+   optional string service_class_name = 2;
+   // Method name of the API call. It can also be a transaction code if we cannot resolve it to a
+   // name. See Binder#getTransactionName.
+   optional string service_method_name = 3;
+   // Total number of API calls.
+   optional int64 call_count = 4;
+   // Number of exceptions thrown by the API.
+   optional int64 exception_count = 5;
+   // Total latency of all API calls.
+   // Average can be computed using total_latency_micros / call_count.
+   optional int64 total_latency_micros = 6;
+   // Maximum latency of one API call.
+   optional int64 max_latency_micros = 7;
+   // Total CPU usage of all API calls.
+   optional int64 total_cpu_micros = 8;
+   // Maximum CPU usage of one API call.
+   optional int64 max_cpu_micros = 9;
+   // Maximum parcel reply size of one API call.
+   optional int64 max_reply_size_bytes = 10;
+   // Maximum parcel request size of one API call.
+   optional int64 max_request_size_bytes = 11;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 06edff9e..160d6e8 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -171,7 +171,14 @@
           1 * NS_PER_SEC,
           new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
         // temperature
-        {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}}};
+        {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}},
+        // binder_calls
+        {android::util::BINDER_CALLS,
+         {{4, 5, 6, 8},
+          {2, 3, 7, 9, 10, 11},
+          1 * NS_PER_SEC,
+          new StatsCompanionServicePuller(android::util::BINDER_CALLS)}}
+        };
 
 StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
 }
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 038cb95..a955511 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -100,6 +100,7 @@
 const int FIELD_ID_UID_MAP_DELETED_APPS = 4;
 
 const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
+        {android::util::BINDER_CALLS, {6000, 10000}},
         {android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
 };
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6bac52d..6638dd9 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1086,7 +1086,7 @@
      *
      * @param savedInstanceState contains the saved state
      */
-    final void performRestoreInstanceState(Bundle savedInstanceState) {
+    final void performRestoreInstanceState(@NonNull Bundle savedInstanceState) {
         onRestoreInstanceState(savedInstanceState);
         restoreManagedDialogs(savedInstanceState);
     }
@@ -1100,8 +1100,8 @@
      * @param savedInstanceState contains the saved state
      * @param persistentState contains the persistable saved state
      */
-    final void performRestoreInstanceState(Bundle savedInstanceState,
-            PersistableBundle persistentState) {
+    final void performRestoreInstanceState(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
         onRestoreInstanceState(savedInstanceState, persistentState);
         if (savedInstanceState != null) {
             restoreManagedDialogs(savedInstanceState);
@@ -1128,7 +1128,7 @@
      * @see #onResume
      * @see #onSaveInstanceState
      */
-    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
         if (mWindow != null) {
             Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
             if (windowState != null) {
@@ -1149,8 +1149,12 @@
      *
      * <p>If this method is called {@link #onRestoreInstanceState(Bundle)} will not be called.
      *
-     * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
-     * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}.
+     * <p>At least one of {@code savedInstanceState} or {@code persistentState} will not be null.
+     *
+     * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}
+     *     or null.
+     * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}
+     *     or null.
      *
      * @see #onRestoreInstanceState(Bundle)
      * @see #onCreate
@@ -1158,8 +1162,8 @@
      * @see #onResume
      * @see #onSaveInstanceState
      */
-    public void onRestoreInstanceState(Bundle savedInstanceState,
-            PersistableBundle persistentState) {
+    public void onRestoreInstanceState(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
         if (savedInstanceState != null) {
             onRestoreInstanceState(savedInstanceState);
         }
@@ -1545,7 +1549,7 @@
      *
      * @param outState The bundle to save the state to.
      */
-    final void performSaveInstanceState(Bundle outState) {
+    final void performSaveInstanceState(@NonNull Bundle outState) {
         onSaveInstanceState(outState);
         saveManagedDialogs(outState);
         mActivityTransitionState.saveState(outState);
@@ -1562,7 +1566,8 @@
      * @param outState The bundle to save the state to.
      * @param outPersistentState The bundle to save persistent state to.
      */
-    final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+    final void performSaveInstanceState(@NonNull Bundle outState,
+            @NonNull PersistableBundle outPersistentState) {
         onSaveInstanceState(outState, outPersistentState);
         saveManagedDialogs(outState);
         storeHasCurrentPermissionRequest(outState);
@@ -1618,7 +1623,7 @@
      * @see #onRestoreInstanceState
      * @see #onPause
      */
-    protected void onSaveInstanceState(Bundle outState) {
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
         outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
 
         outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
@@ -1648,7 +1653,8 @@
      * @see #onRestoreInstanceState(Bundle, PersistableBundle)
      * @see #onPause
      */
-    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+    public void onSaveInstanceState(@NonNull Bundle outState,
+            @NonNull PersistableBundle outPersistentState) {
         onSaveInstanceState(outState);
     }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3f579bc..f27b286 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3080,32 +3080,16 @@
          */
         public int processState;
 
-        /**
-         * Whether the app is focused in multi-window environment.
-         * @hide
-         */
-        public boolean isFocused;
-
-        /**
-         * Copy of {@link com.android.server.am.ProcessRecord#lastActivityTime} of the process.
-         * @hide
-         */
-        public long lastActivityTime;
-
         public RunningAppProcessInfo() {
             importance = IMPORTANCE_FOREGROUND;
             importanceReasonCode = REASON_UNKNOWN;
             processState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-            isFocused = false;
-            lastActivityTime = 0;
         }
 
         public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
             processName = pProcessName;
             pid = pPid;
             pkgList = pArr;
-            isFocused = false;
-            lastActivityTime = 0;
         }
 
         public int describeContents() {
@@ -3126,8 +3110,6 @@
             ComponentName.writeToParcel(importanceReasonComponent, dest);
             dest.writeInt(importanceReasonImportance);
             dest.writeInt(processState);
-            dest.writeInt(isFocused ? 1 : 0);
-            dest.writeLong(lastActivityTime);
         }
 
         public void readFromParcel(Parcel source) {
@@ -3144,8 +3126,6 @@
             importanceReasonComponent = ComponentName.readFromParcel(source);
             importanceReasonImportance = source.readInt();
             processState = source.readInt();
-            isFocused = source.readInt() != 0;
-            lastActivityTime = source.readLong();
         }
 
         public static final Creator<RunningAppProcessInfo> CREATOR =
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f3c6731..2daa577 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -35,6 +35,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.PendingTransactionActions.StopInfo;
 import android.app.servertransaction.TransactionExecutor;
@@ -176,6 +177,7 @@
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -257,6 +259,8 @@
     final H mH = new H();
     final Executor mExecutor = new HandlerExecutor(mH);
     final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
+    final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
+            Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
     // List of new activities (via ActivityRecord.nextIdle) that should
     // be reported when next we idle.
     ActivityClientRecord mNewActivities = null;
@@ -4474,6 +4478,11 @@
     }
 
     @Override
+    public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+        return mActivitiesToBeDestroyed;
+    }
+
+    @Override
     public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
             boolean getNonConfigInstance, String reason) {
         ActivityClientRecord r = performDestroyActivity(token, finishing,
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 4531f53..6a58d9b 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -17,6 +17,8 @@
 package android.app;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentCallbacks;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
@@ -58,13 +60,13 @@
     public LoadedApk mLoadedApk;
 
     public interface ActivityLifecycleCallbacks {
-        void onActivityCreated(Activity activity, Bundle savedInstanceState);
-        void onActivityStarted(Activity activity);
-        void onActivityResumed(Activity activity);
-        void onActivityPaused(Activity activity);
-        void onActivityStopped(Activity activity);
-        void onActivitySaveInstanceState(Activity activity, Bundle outState);
-        void onActivityDestroyed(Activity activity);
+        void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
+        void onActivityStarted(@NonNull Activity activity);
+        void onActivityResumed(@NonNull Activity activity);
+        void onActivityPaused(@NonNull Activity activity);
+        void onActivityStopped(@NonNull Activity activity);
+        void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
+        void onActivityDestroyed(@NonNull Activity activity);
     }
 
     /**
@@ -213,7 +215,8 @@
         mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
     }
 
-    /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) {
+    /* package */ void dispatchActivityCreated(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
@@ -223,7 +226,7 @@
         }
     }
 
-    /* package */ void dispatchActivityStarted(Activity activity) {
+    /* package */ void dispatchActivityStarted(@NonNull Activity activity) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
@@ -232,7 +235,7 @@
         }
     }
 
-    /* package */ void dispatchActivityResumed(Activity activity) {
+    /* package */ void dispatchActivityResumed(@NonNull Activity activity) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
@@ -241,7 +244,7 @@
         }
     }
 
-    /* package */ void dispatchActivityPaused(Activity activity) {
+    /* package */ void dispatchActivityPaused(@NonNull Activity activity) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
@@ -250,7 +253,7 @@
         }
     }
 
-    /* package */ void dispatchActivityStopped(Activity activity) {
+    /* package */ void dispatchActivityStopped(@NonNull Activity activity) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
@@ -259,7 +262,8 @@
         }
     }
 
-    /* package */ void dispatchActivitySaveInstanceState(Activity activity, Bundle outState) {
+    /* package */ void dispatchActivitySaveInstanceState(@NonNull Activity activity,
+            @NonNull Bundle outState) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
@@ -269,7 +273,7 @@
         }
     }
 
-    /* package */ void dispatchActivityDestroyed(Activity activity) {
+    /* package */ void dispatchActivityDestroyed(@NonNull Activity activity) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i=0; i<callbacks.length; i++) {
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d9c7cf3..193f933 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -16,6 +16,7 @@
 package android.app;
 
 import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.TransactionExecutor;
 import android.content.Intent;
@@ -29,6 +30,7 @@
 import com.android.internal.content.ReferrerIntent;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
@@ -78,6 +80,9 @@
     // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
     // and deliver callbacks.
 
+    /** Get activity and its corresponding transaction item which are going to destroy. */
+    public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
+
     /** Destroy the activity. */
     public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
             boolean getNonConfigInstance, String reason);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 34be41b6..d9969a7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1313,7 +1313,8 @@
      * @param activity The activity being restored.
      * @param savedInstanceState The previously saved state being restored.
      */
-    public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
+    public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
+            @NonNull Bundle savedInstanceState) {
         activity.performRestoreInstanceState(savedInstanceState);
     }
 
@@ -1322,11 +1323,12 @@
      * method.  The default implementation simply calls through to that method.
      *
      * @param activity The activity being restored.
-     * @param savedInstanceState The previously saved state being restored.
+     * @param savedInstanceState The previously saved state being restored (or null).
      * @param persistentState The previously persisted state (or null)
      */
-    public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState,
-            PersistableBundle persistentState) {
+    public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
         activity.performRestoreInstanceState(savedInstanceState, persistentState);
     }
 
@@ -1335,11 +1337,12 @@
      * The default implementation simply calls through to that method.
      *
      * @param activity The activity being created.
-     * @param icicle The previously frozen state (or null) to pass through to
+     * @param savedInstanceState The previously saved state (or null) to pass through to
      *               onPostCreate().
      */
-    public void callActivityOnPostCreate(Activity activity, Bundle icicle) {
-        activity.onPostCreate(icicle);
+    public void callActivityOnPostCreate(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState) {
+        activity.onPostCreate(savedInstanceState);
     }
 
     /**
@@ -1347,12 +1350,14 @@
      * The default implementation simply calls through to that method.
      *
      * @param activity The activity being created.
-     * @param icicle The previously frozen state (or null) to pass through to
+     * @param savedInstanceState The previously frozen state (or null) to pass through to
      *               onPostCreate().
+     * @param persistentState The previously persisted state (or null)
      */
-    public void callActivityOnPostCreate(Activity activity, Bundle icicle,
-            PersistableBundle persistentState) {
-        activity.onPostCreate(icicle, persistentState);
+    public void callActivityOnPostCreate(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
+        activity.onPostCreate(savedInstanceState, persistentState);
     }
 
     /**
@@ -1439,7 +1444,8 @@
      * @param activity The activity being saved.
      * @param outState The bundle to pass to the call.
      */
-    public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
+    public void callActivityOnSaveInstanceState(@NonNull Activity activity,
+            @NonNull Bundle outState) {
         activity.performSaveInstanceState(outState);
     }
 
@@ -1450,8 +1456,8 @@
      * @param outState The bundle to pass to the call.
      * @param outPersistentState The persistent bundle to pass to the call.
      */
-    public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
-            PersistableBundle outPersistentState) {
+    public void callActivityOnSaveInstanceState(@NonNull Activity activity,
+            @NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
         activity.performSaveInstanceState(outState, outPersistentState);
     }
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 05bf6bf..74d3c0d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -202,6 +202,11 @@
      */
     private static final int MAX_REPLY_HISTORY = 5;
 
+    /**
+     * Maximum numbers of action buttons in a notification.
+     * @hide
+     */
+    public static final int MAX_ACTION_BUTTONS = 3;
 
     /**
      * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
@@ -3151,8 +3156,6 @@
         public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
                 = "android.rebuild.hudViewActionCount";
 
-        private static final int MAX_ACTION_BUTTONS = 3;
-
         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
                 SystemProperties.getBoolean("notifications.only_title", true);
 
@@ -4473,9 +4476,9 @@
                 mTextColorsAreForBackground = backgroundColor;
                 if (!hasForegroundColor() || !isColorized()) {
                     mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
-                            backgroundColor);
+                            backgroundColor, mInNightMode);
                     mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
-                            backgroundColor);
+                            backgroundColor, mInNightMode);
                     if (backgroundColor != COLOR_DEFAULT && isColorized()) {
                         mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
                                 mPrimaryTextColor, backgroundColor, 4.5);
@@ -5260,7 +5263,7 @@
                     // background color
                     background = outResultColor[0].getDefaultColor();
                     int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
-                            background);
+                            background, mInNightMode);
                     button.setTextColor(R.id.action0, textColor);
                     rippleColor = textColor;
                 } else if (mN.color != COLOR_DEFAULT && !isColorized() && mTintActionButtons) {
@@ -5440,7 +5443,7 @@
                     com.android.internal.R.color.notification_material_background_color);
             if (mN.color == COLOR_DEFAULT) {
                 ensureColors();
-                color = ContrastColorUtil.resolveDefaultColor(mContext, background);
+                color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode);
             } else {
                 color = ContrastColorUtil.resolveContrastColor(mContext, mN.color,
                         background, mInNightMode);
@@ -5459,7 +5462,8 @@
             }
             int background = mContext.getColor(
                     com.android.internal.R.color.notification_material_background_color);
-            mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background);
+            mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
+                    mInNightMode);
             if (Color.alpha(mNeutralColor) < 255) {
                 // alpha doesn't go well for color filters, so let's blend it manually
                 mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
@@ -7162,8 +7166,8 @@
         }
 
         public static final class Message {
-
-            static final String KEY_TEXT = "text";
+            /** @hide */
+            public static final String KEY_TEXT = "text";
             static final String KEY_TIMESTAMP = "time";
             static final String KEY_SENDER = "sender";
             static final String KEY_SENDER_PERSON = "sender_person";
@@ -7830,10 +7834,13 @@
 
             // If the action buttons should not be tinted, then just use the default
             // notification color. Otherwise, just use the passed-in color.
+            Configuration currentConfig = mBuilder.mContext.getResources().getConfiguration();
+            boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                    == Configuration.UI_MODE_NIGHT_YES;
             int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized()
                     ? color
                     : ContrastColorUtil.resolveColor(mBuilder.mContext,
-                            Notification.COLOR_DEFAULT);
+                            Notification.COLOR_DEFAULT, inNightMode);
 
             button.setDrawableTint(R.id.action0, false, tintColor,
                     PorterDuff.Mode.SRC_ATOP);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1ad3054..03fd139 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -38,6 +38,7 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.Arrays;
 
 /**
@@ -942,6 +943,32 @@
         return result;
     }
 
+    /** @hide */
+    public void dump(PrintWriter pw, String prefix, boolean redacted) {
+        String redactedName = redacted ? TextUtils.trimToLengthWithEllipsis(mName, 3) : mName;
+        String output = "NotificationChannel{"
+                + "mId='" + mId + '\''
+                + ", mName=" + redactedName
+                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+                + ", mImportance=" + mImportance
+                + ", mBypassDnd=" + mBypassDnd
+                + ", mLockscreenVisibility=" + mLockscreenVisibility
+                + ", mSound=" + mSound
+                + ", mLights=" + mLights
+                + ", mLightColor=" + mLightColor
+                + ", mVibration=" + Arrays.toString(mVibration)
+                + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
+                + ", mFgServiceShown=" + mFgServiceShown
+                + ", mVibrationEnabled=" + mVibrationEnabled
+                + ", mShowBadge=" + mShowBadge
+                + ", mDeleted=" + mDeleted
+                + ", mGroup='" + mGroup + '\''
+                + ", mAudioAttributes=" + mAudioAttributes
+                + ", mBlockableSystem=" + mBlockableSystem
+                + '}';
+        pw.println(prefix + output);
+    }
+
     @Override
     public String toString() {
         return "NotificationChannel{"
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 08ad2f0..2c1e59b 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -164,6 +164,31 @@
         ObjectPool.recycle(this);
     }
 
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(64);
+        sb.append("ClientTransaction{");
+        if (mActivityToken != null) {
+            sb.append(" a:").append(Integer.toHexString(System.identityHashCode(mActivityToken)));
+        }
+        if (mActivityCallbacks != null && !mActivityCallbacks.isEmpty()) {
+            sb.append(" c:");
+            final int size = mActivityCallbacks.size();
+            for (int i = 0; i < size; i++) {
+                sb.append(mActivityCallbacks.get(i).getClass().getSimpleName());
+                if (i < size - 1) {
+                    sb.append(",");
+                }
+            }
+        }
+        if (mLifecycleStateRequest != null) {
+            sb.append(" s:");
+            sb.append(mLifecycleStateRequest.getClass().getSimpleName());
+        }
+        sb.append(" }");
+        return sb.toString();
+    }
+
 
     // Parcelable implementation
 
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index b443166..5941486 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,6 +33,11 @@
     private int mConfigChanges;
 
     @Override
+    public void preExecute(ClientTransactionHandler client, IBinder token) {
+        client.getActivitiesToBeDestroyed().put(token, this);
+    }
+
+    @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 43a2b4c..503e18b6 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -35,6 +35,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Class that manages transaction execution in the correct order.
@@ -63,6 +64,24 @@
      */
     public void execute(ClientTransaction transaction) {
         final IBinder token = transaction.getActivityToken();
+        if (token != null) {
+            final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
+                    mTransactionHandler.getActivitiesToBeDestroyed();
+            final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
+            if (destroyItem != null) {
+                if (transaction.getLifecycleStateRequest() == destroyItem) {
+                    // It is going to execute the transaction that will destroy activity with the
+                    // token, so the corresponding to-be-destroyed record can be removed.
+                    activitiesToBeDestroyed.remove(token);
+                }
+                if (mTransactionHandler.getActivityClient(token) == null) {
+                    // The activity has not been created but has been requested to destroy, so all
+                    // transactions for the token are just like being cancelled.
+                    Slog.w(TAG, "Skip pre-destroyed " + transaction);
+                    return;
+                }
+            }
+        }
         log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);
 
         executeCallbacks(transaction);
@@ -76,7 +95,7 @@
     @VisibleForTesting
     public void executeCallbacks(ClientTransaction transaction) {
         final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
-        if (callbacks == null) {
+        if (callbacks == null || callbacks.isEmpty()) {
             // No callbacks to execute, return early.
             return;
         }
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 2d490a0..9d8c318 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -39,6 +39,8 @@
 
 import dalvik.system.CloseGuard;
 
+import libcore.io.IoUtils;
+
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -560,14 +562,17 @@
         return ContentProvider.coerceToLocalContentProvider(mContentProvider);
     }
 
+    /**
+     * Closes the given object quietly, ignoring any checked exceptions. Does
+     * nothing if the given object is {@code null}.
+     */
+    public static void closeQuietly(ContentProviderClient client) {
+        IoUtils.closeQuietly(client);
+    }
+
     /** {@hide} */
     public static void releaseQuietly(ContentProviderClient client) {
-        if (client != null) {
-            try {
-                client.release();
-            } catch (Exception ignored) {
-            }
-        }
+        IoUtils.closeQuietly(client);
     }
 
     private class NotRespondingRunnable implements Runnable {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 4baf263..86bd30c 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -747,6 +747,9 @@
         if (faceDetectMode == null) {
             Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
             faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE;
+        } else if (faceDetectMode > CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+            // Face detect mode is larger than FULL, assuming the mode is FULL
+            faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL;
         } else {
             if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
                 return new Face[0];
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0a66e91..3de9de3 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -836,10 +836,8 @@
          * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
          *
          * @param remaining The number of remaining steps
-         * @param vendorMsg Vendor feedback about the current enroll attempt. Use it to customize
-         *                  the GUI according to vendor's requirements.
          */
-        public void onEnrollmentProgress(int remaining, long vendorMsg) {
+        public void onEnrollmentProgress(int remaining) {
         }
     }
 
@@ -920,7 +918,7 @@
         public void handleMessage(android.os.Message msg) {
             switch (msg.what) {
                 case MSG_ENROLL_RESULT:
-                    sendEnrollResult((EnrollResultMsg) msg.obj);
+                    sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
                     break;
                 case MSG_ACQUIRED:
                     sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
@@ -951,8 +949,6 @@
             Log.e(TAG, "Received MSG_REMOVED, but face is null");
             return;
         }
-
-
         mRemovalCallback.onRemovalSucceeded(face, remaining);
     }
 
@@ -972,11 +968,9 @@
         }
     }
 
-    private void sendEnrollResult(EnrollResultMsg faceWrapper) {
+    private void sendEnrollResult(Face face, int remaining) {
         if (mEnrollmentCallback != null) {
-            int remaining = faceWrapper.getRemaining();
-            long vendorMsg = faceWrapper.getVendorMsg();
-            mEnrollmentCallback.onEnrollmentProgress(remaining, vendorMsg);
+            mEnrollmentCallback.onEnrollmentProgress(remaining);
         }
     }
 
@@ -1010,28 +1004,4 @@
             mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
         }
     }
-
-    private class EnrollResultMsg {
-        private final Face mFace;
-        private final int mRemaining;
-        private final long mVendorMsg;
-
-        EnrollResultMsg(Face face, int remaining, long vendorMsg) {
-            mFace = face;
-            mRemaining = remaining;
-            mVendorMsg = vendorMsg;
-        }
-
-        Face getFace() {
-            return mFace;
-        }
-
-        long getVendorMsg() {
-            return mVendorMsg;
-        }
-
-        int getRemaining() {
-            return mRemaining;
-        }
-    }
 }
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 4d8e734..562065e 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -85,7 +85,7 @@
         mAppId = in.readLong();
         mAppVersion = in.readInt();
         mVersionRestrictionMask = in.readInt();
-        mAppIdVendorMask = in.readInt();
+        mAppIdVendorMask = in.readLong();
     }
 
     public int describeContents() {
@@ -93,7 +93,6 @@
     }
 
     public void writeToParcel(Parcel out, int flags) {
-
         out.writeLong(mAppId);
         out.writeInt(mAppVersion);
         out.writeInt(mVersionRestrictionMask);
diff --git a/core/java/android/hardware/radio/RadioManager.aidl b/core/java/android/hardware/radio/RadioManager.aidl
index 8a39388..34c05d8 100644
--- a/core/java/android/hardware/radio/RadioManager.aidl
+++ b/core/java/android/hardware/radio/RadioManager.aidl
@@ -20,6 +20,9 @@
 parcelable RadioManager.BandConfig;
 
 /** @hide */
+parcelable RadioManager.BandDescriptor;
+
+/** @hide */
 parcelable RadioManager.ModuleProperties;
 
 /** @hide */
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 88d6e84..9fccd1e 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -53,32 +53,35 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 import java.util.zip.CRC32;
 import java.util.zip.CheckedInputStream;
 
 /**
- * Tools for managing files.  Not for public consumption.
- * @hide
+ * Utility methods useful for working with files.
  */
 public class FileUtils {
     private static final String TAG = "FileUtils";
 
-    public static final int S_IRWXU = 00700;
-    public static final int S_IRUSR = 00400;
-    public static final int S_IWUSR = 00200;
-    public static final int S_IXUSR = 00100;
+    /** {@hide} */ public static final int S_IRWXU = 00700;
+    /** {@hide} */ public static final int S_IRUSR = 00400;
+    /** {@hide} */ public static final int S_IWUSR = 00200;
+    /** {@hide} */ public static final int S_IXUSR = 00100;
 
-    public static final int S_IRWXG = 00070;
-    public static final int S_IRGRP = 00040;
-    public static final int S_IWGRP = 00020;
-    public static final int S_IXGRP = 00010;
+    /** {@hide} */ public static final int S_IRWXG = 00070;
+    /** {@hide} */ public static final int S_IRGRP = 00040;
+    /** {@hide} */ public static final int S_IWGRP = 00020;
+    /** {@hide} */ public static final int S_IXGRP = 00010;
 
-    public static final int S_IRWXO = 00007;
-    public static final int S_IROTH = 00004;
-    public static final int S_IWOTH = 00002;
-    public static final int S_IXOTH = 00001;
+    /** {@hide} */ public static final int S_IRWXO = 00007;
+    /** {@hide} */ public static final int S_IROTH = 00004;
+    /** {@hide} */ public static final int S_IWOTH = 00002;
+    /** {@hide} */ public static final int S_IXOTH = 00001;
+
+    private FileUtils() {
+    }
 
     /** Regular expression for safe filenames: no spaces or metacharacters.
       *
@@ -94,6 +97,9 @@
 
     private static final long COPY_CHECKPOINT_BYTES = 524288;
 
+    /**
+     * Listener that is called periodically as progress is made.
+     */
     public interface ProgressListener {
         public void onProgress(long progress);
     }
@@ -105,6 +111,7 @@
      * @param uid to apply through {@code chown}, or -1 to leave unchanged
      * @param gid to apply through {@code chown}, or -1 to leave unchanged
      * @return 0 on success, otherwise errno.
+     * @hide
      */
     public static int setPermissions(File path, int mode, int uid, int gid) {
         return setPermissions(path.getAbsolutePath(), mode, uid, gid);
@@ -117,6 +124,7 @@
      * @param uid to apply through {@code chown}, or -1 to leave unchanged
      * @param gid to apply through {@code chown}, or -1 to leave unchanged
      * @return 0 on success, otherwise errno.
+     * @hide
      */
     public static int setPermissions(String path, int mode, int uid, int gid) {
         try {
@@ -145,6 +153,7 @@
      * @param uid to apply through {@code chown}, or -1 to leave unchanged
      * @param gid to apply through {@code chown}, or -1 to leave unchanged
      * @return 0 on success, otherwise errno.
+     * @hide
      */
     public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
         try {
@@ -166,7 +175,14 @@
         return 0;
     }
 
-    public static void copyPermissions(File from, File to) throws IOException {
+    /**
+     * Copy the owner UID, owner GID, and mode bits from one file to another.
+     *
+     * @param from File where attributes should be copied from.
+     * @param to File where attributes should be copied to.
+     * @hide
+     */
+    public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
         try {
             final StructStat stat = Os.stat(from.getAbsolutePath());
             Os.chmod(to.getAbsolutePath(), stat.st_mode);
@@ -177,8 +193,10 @@
     }
 
     /**
-     * Return owning UID of given path, otherwise -1.
+     * @deprecated use {@link Os#stat(String)} instead.
+     * @hide
      */
+    @Deprecated
     public static int getUid(String path) {
         try {
             return Os.stat(path).st_uid;
@@ -190,6 +208,8 @@
     /**
      * Perform an fsync on the given FileOutputStream.  The stream at this
      * point must be flushed but not yet closed.
+     *
+     * @hide
      */
     public static boolean sync(FileOutputStream stream) {
         try {
@@ -204,6 +224,7 @@
 
     /**
      * @deprecated use {@link #copy(File, File)} instead.
+     * @hide
      */
     @Deprecated
     public static boolean copyFile(File srcFile, File destFile) {
@@ -217,6 +238,7 @@
 
     /**
      * @deprecated use {@link #copy(File, File)} instead.
+     * @hide
      */
     @Deprecated
     public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
@@ -227,6 +249,7 @@
 
     /**
      * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+     * @hide
      */
     @Deprecated
     public static boolean copyToFile(InputStream inputStream, File destFile) {
@@ -240,6 +263,7 @@
 
     /**
      * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+     * @hide
      */
     @Deprecated
     public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
@@ -265,7 +289,7 @@
      * @return number of bytes copied.
      */
     public static long copy(@NonNull File from, @NonNull File to) throws IOException {
-        return copy(from, to, null, null);
+        return copy(from, to, null, null, null);
     }
 
     /**
@@ -274,16 +298,17 @@
      * Attempts to use several optimization strategies to copy the data in the
      * kernel before falling back to a userspace copy as a last resort.
      *
-     * @param listener to be periodically notified as the copy progresses.
      * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
      * @return number of bytes copied.
      */
     public static long copy(@NonNull File from, @NonNull File to,
-            @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
-            throws IOException {
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
         try (FileInputStream in = new FileInputStream(from);
                 FileOutputStream out = new FileOutputStream(to)) {
-            return copy(in, out, listener, signal);
+            return copy(in, out, signal, executor, listener);
         }
     }
 
@@ -296,7 +321,7 @@
      * @return number of bytes copied.
      */
     public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
-        return copy(in, out, null, null);
+        return copy(in, out, null, null, null);
     }
 
     /**
@@ -305,22 +330,23 @@
      * Attempts to use several optimization strategies to copy the data in the
      * kernel before falling back to a userspace copy as a last resort.
      *
-     * @param listener to be periodically notified as the copy progresses.
      * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
      * @return number of bytes copied.
      */
     public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
-            @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
-            throws IOException {
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
         if (ENABLE_COPY_OPTIMIZATIONS) {
             if (in instanceof FileInputStream && out instanceof FileOutputStream) {
                 return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
-                        listener, signal);
+                        signal, executor, listener);
             }
         }
 
         // Worse case fallback to userspace
-        return copyInternalUserspace(in, out, listener, signal);
+        return copyInternalUserspace(in, out, signal, executor, listener);
     }
 
     /**
@@ -333,7 +359,7 @@
      */
     public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
             throws IOException {
-        return copy(in, out, null, null);
+        return copy(in, out, null, null, null);
     }
 
     /**
@@ -342,14 +368,15 @@
      * Attempts to use several optimization strategies to copy the data in the
      * kernel before falling back to a userspace copy as a last resort.
      *
-     * @param listener to be periodically notified as the copy progresses.
      * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
      * @return number of bytes copied.
      */
     public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
-            @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
-            throws IOException {
-        return copy(in, out, listener, signal, Long.MAX_VALUE);
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
+        return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
     }
 
     /**
@@ -358,22 +385,24 @@
      * Attempts to use several optimization strategies to copy the data in the
      * kernel before falling back to a userspace copy as a last resort.
      *
-     * @param listener to be periodically notified as the copy progresses.
-     * @param signal to signal if the copy should be cancelled early.
      * @param count the number of bytes to copy.
+     * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
      * @return number of bytes copied.
+     * @hide
      */
-    public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
-            @Nullable ProgressListener listener, @Nullable CancellationSignal signal, long count)
-            throws IOException {
+    public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
         if (ENABLE_COPY_OPTIMIZATIONS) {
             try {
                 final StructStat st_in = Os.fstat(in);
                 final StructStat st_out = Os.fstat(out);
                 if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
-                    return copyInternalSendfile(in, out, listener, signal, count);
+                    return copyInternalSendfile(in, out, count, signal, executor, listener);
                 } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
-                    return copyInternalSplice(in, out, listener, signal, count);
+                    return copyInternalSplice(in, out, count, signal, executor, listener);
                 }
             } catch (ErrnoException e) {
                 throw e.rethrowAsIOException();
@@ -381,15 +410,17 @@
         }
 
         // Worse case fallback to userspace
-        return copyInternalUserspace(in, out, listener, signal, count);
+        return copyInternalUserspace(in, out, count, signal, executor, listener);
     }
 
     /**
      * Requires one of input or output to be a pipe.
+     *
+     * @hide
      */
     @VisibleForTesting
-    public static long copyInternalSplice(FileDescriptor in, FileDescriptor out,
-            ProgressListener listener, CancellationSignal signal, long count)
+    public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
             throws ErrnoException {
         long progress = 0;
         long checkpoint = 0;
@@ -405,24 +436,32 @@
                 if (signal != null) {
                     signal.throwIfCanceled();
                 }
-                if (listener != null) {
-                    listener.onProgress(progress);
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
                 }
                 checkpoint = 0;
             }
         }
-        if (listener != null) {
-            listener.onProgress(progress);
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
         }
         return progress;
     }
 
     /**
      * Requires both input and output to be a regular file.
+     *
+     * @hide
      */
     @VisibleForTesting
-    public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out,
-            ProgressListener listener, CancellationSignal signal, long count)
+    public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
             throws ErrnoException {
         long progress = 0;
         long checkpoint = 0;
@@ -437,33 +476,52 @@
                 if (signal != null) {
                     signal.throwIfCanceled();
                 }
-                if (listener != null) {
-                    listener.onProgress(progress);
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
                 }
                 checkpoint = 0;
             }
         }
-        if (listener != null) {
-            listener.onProgress(progress);
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
         }
         return progress;
     }
 
+    /** {@hide} */
+    @Deprecated
     @VisibleForTesting
     public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
-            ProgressListener listener, CancellationSignal signal, long count) throws IOException {
+            ProgressListener listener, CancellationSignal signal, long count)
+            throws IOException {
+        return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws IOException {
         if (count != Long.MAX_VALUE) {
             return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
-                    new FileOutputStream(out), listener, signal);
+                    new FileOutputStream(out), signal, executor, listener);
         } else {
             return copyInternalUserspace(new FileInputStream(in),
-                    new FileOutputStream(out), listener, signal);
+                    new FileOutputStream(out), signal, executor, listener);
         }
     }
 
+    /** {@hide} */
     @VisibleForTesting
     public static long copyInternalUserspace(InputStream in, OutputStream out,
-            ProgressListener listener, CancellationSignal signal) throws IOException {
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws IOException {
         long progress = 0;
         long checkpoint = 0;
         byte[] buffer = new byte[8192];
@@ -479,14 +537,20 @@
                 if (signal != null) {
                     signal.throwIfCanceled();
                 }
-                if (listener != null) {
-                    listener.onProgress(progress);
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
                 }
                 checkpoint = 0;
             }
         }
-        if (listener != null) {
-            listener.onProgress(progress);
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
         }
         return progress;
     }
@@ -494,6 +558,7 @@
     /**
      * Check if a filename is "safe" (no metacharacters or spaces).
      * @param file  The file to check
+     * @hide
      */
     public static boolean isFilenameSafe(File file) {
         // Note, we check whether it matches what's known to be safe,
@@ -509,6 +574,7 @@
      * @param ellipsis to add of the file was truncated (can be null)
      * @return the contents of the file, possibly truncated
      * @throws IOException if something goes wrong reading the file
+     * @hide
      */
     public static String readTextFile(File file, int max, String ellipsis) throws IOException {
         InputStream input = new FileInputStream(file);
@@ -563,13 +629,16 @@
         }
     }
 
+    /** {@hide} */
     public static void stringToFile(File file, String string) throws IOException {
         stringToFile(file.getAbsolutePath(), string);
     }
 
-    /*
+    /**
      * Writes the bytes given in {@code content} to the file whose absolute path
      * is {@code filename}.
+     *
+     * @hide
      */
     public static void bytesToFile(String filename, byte[] content) throws IOException {
         if (filename.startsWith("/proc/")) {
@@ -592,18 +661,23 @@
      * @param filename
      * @param string
      * @throws IOException
+     * @hide
      */
     public static void stringToFile(String filename, String string) throws IOException {
         bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
     }
 
     /**
-     * Computes the checksum of a file using the CRC32 checksum routine.
-     * The value of the checksum is returned.
+     * Computes the checksum of a file using the CRC32 checksum routine. The
+     * value of the checksum is returned.
      *
-     * @param file  the file to checksum, must not be null
+     * @param file the file to checksum, must not be null
      * @return the checksum value or an exception is thrown.
+     * @deprecated this is a weak hashing algorithm, and should not be used due
+     *             to its potential for collision.
+     * @hide
      */
+    @Deprecated
     public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
         CRC32 checkSummer = new CRC32();
         CheckedInputStream cis = null;
@@ -632,6 +706,7 @@
      * @param minCount Always keep at least this many files.
      * @param minAgeMs Always keep files younger than this age, in milliseconds.
      * @return if any files were deleted.
+     * @hide
      */
     public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
         if (minCount < 0 || minAgeMs < 0) {
@@ -673,6 +748,8 @@
      * Both files <em>must</em> have been resolved using
      * {@link File#getCanonicalFile()} to avoid symlink or path traversal
      * attacks.
+     *
+     * @hide
      */
     public static boolean contains(File[] dirs, File file) {
         for (File dir : dirs) {
@@ -690,12 +767,15 @@
      * Both files <em>must</em> have been resolved using
      * {@link File#getCanonicalFile()} to avoid symlink or path traversal
      * attacks.
+     *
+     * @hide
      */
     public static boolean contains(File dir, File file) {
         if (dir == null || file == null) return false;
         return contains(dir.getAbsolutePath(), file.getAbsolutePath());
     }
 
+    /** {@hide} */
     public static boolean contains(String dirPath, String filePath) {
         if (dirPath.equals(filePath)) {
             return true;
@@ -706,6 +786,7 @@
         return filePath.startsWith(dirPath);
     }
 
+    /** {@hide} */
     public static boolean deleteContentsAndDir(File dir) {
         if (deleteContents(dir)) {
             return dir.delete();
@@ -714,6 +795,7 @@
         }
     }
 
+    /** {@hide} */
     public static boolean deleteContents(File dir) {
         File[] files = dir.listFiles();
         boolean success = true;
@@ -743,6 +825,8 @@
 
     /**
      * Check if given filename is valid for an ext4 filesystem.
+     *
+     * @hide
      */
     public static boolean isValidExtFilename(String name) {
         return (name != null) && name.equals(buildValidExtFilename(name));
@@ -751,6 +835,8 @@
     /**
      * Mutate the given filename to make it valid for an ext4 filesystem,
      * replacing any invalid characters with "_".
+     *
+     * @hide
      */
     public static String buildValidExtFilename(String name) {
         if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
@@ -792,6 +878,8 @@
 
     /**
      * Check if given filename is valid for a FAT filesystem.
+     *
+     * @hide
      */
     public static boolean isValidFatFilename(String name) {
         return (name != null) && name.equals(buildValidFatFilename(name));
@@ -800,6 +888,8 @@
     /**
      * Mutate the given filename to make it valid for a FAT filesystem,
      * replacing any invalid characters with "_".
+     *
+     * @hide
      */
     public static String buildValidFatFilename(String name) {
         if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
@@ -820,6 +910,7 @@
         return res.toString();
     }
 
+    /** {@hide} */
     @VisibleForTesting
     public static String trimFilename(String str, int maxBytes) {
         final StringBuilder res = new StringBuilder(str);
@@ -827,6 +918,7 @@
         return res.toString();
     }
 
+    /** {@hide} */
     private static void trimFilename(StringBuilder res, int maxBytes) {
         byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
         if (raw.length > maxBytes) {
@@ -839,12 +931,14 @@
         }
     }
 
+    /** {@hide} */
     public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
         if (path == null) return null;
         final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
         return (result != null) ? result.getAbsolutePath() : null;
     }
 
+    /** {@hide} */
     public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
         if (paths == null) return null;
         final String[] result = new String[paths.length];
@@ -858,6 +952,8 @@
      * Given a path under the "before" directory, rewrite it to live under the
      * "after" directory. For example, {@code /before/foo/bar.txt} would become
      * {@code /after/foo/bar.txt}.
+     *
+     * @hide
      */
     public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
         if (file == null || beforeDir == null || afterDir == null) return null;
@@ -869,6 +965,7 @@
         return null;
     }
 
+    /** {@hide} */
     private static File buildUniqueFileWithExtension(File parent, String name, String ext)
             throws FileNotFoundException {
         File file = buildFile(parent, name, ext);
@@ -895,6 +992,7 @@
      * 'example.txt' or 'example (1).txt', etc.
      *
      * @throws FileNotFoundException
+     * @hide
      */
     public static File buildUniqueFile(File parent, String mimeType, String displayName)
             throws FileNotFoundException {
@@ -905,6 +1003,8 @@
     /**
      * Generates a unique file name under the given parent directory, keeping
      * any extension intact.
+     *
+     * @hide
      */
     public static File buildUniqueFile(File parent, String displayName)
             throws FileNotFoundException {
@@ -929,6 +1029,8 @@
      * If the display name doesn't have an extension that matches the requested MIME type, the
      * extension is regarded as a part of filename and default extension for that MIME type is
      * appended.
+     *
+     * @hide
      */
     public static String[] splitFileName(String mimeType, String displayName) {
         String name;
@@ -975,6 +1077,7 @@
         return new String[] { name, ext };
     }
 
+    /** {@hide} */
     private static File buildFile(File parent, String name, String ext) {
         if (TextUtils.isEmpty(ext)) {
             return new File(parent, name);
@@ -983,6 +1086,7 @@
         }
     }
 
+    /** {@hide} */
     public static @NonNull String[] listOrEmpty(@Nullable File dir) {
         if (dir == null) return EmptyArray.STRING;
         final String[] res = dir.list();
@@ -993,6 +1097,7 @@
         }
     }
 
+    /** {@hide} */
     public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
         if (dir == null) return EMPTY;
         final File[] res = dir.listFiles();
@@ -1003,6 +1108,7 @@
         }
     }
 
+    /** {@hide} */
     public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
         if (dir == null) return EMPTY;
         final File[] res = dir.listFiles(filter);
@@ -1013,6 +1119,7 @@
         }
     }
 
+    /** {@hide} */
     public static @Nullable File newFileOrNull(@Nullable String path) {
         return (path != null) ? new File(path) : null;
     }
@@ -1021,6 +1128,8 @@
      * Creates a directory with name {@code name} under an existing directory {@code baseDir}.
      * Returns a {@code File} object representing the directory on success, {@code null} on
      * failure.
+     *
+     * @hide
      */
     public static @Nullable File createDir(File baseDir, String name) {
         final File dir = new File(baseDir, name);
@@ -1036,6 +1145,8 @@
      * Round the given size of a storage device to a nice round power-of-two
      * value, such as 256MB or 32GB. This avoids showing weird values like
      * "29.5GB" in UI.
+     *
+     * @hide
      */
     public static long roundStorageSize(long size) {
         long val = 1;
@@ -1050,6 +1161,23 @@
         return val * pow;
     }
 
+    /**
+     * Closes the given object quietly, ignoring any checked exceptions. Does
+     * nothing if the given object is {@code null}.
+     */
+    public static void closeQuietly(@Nullable AutoCloseable closeable) {
+        IoUtils.closeQuietly(closeable);
+    }
+
+    /**
+     * Closes the given object quietly, ignoring any checked exceptions. Does
+     * nothing if the given object is {@code null}.
+     */
+    public static void closeQuietly(@Nullable FileDescriptor fd) {
+        IoUtils.closeQuietly(fd);
+    }
+
+    /** {@hide} */
     @VisibleForTesting
     public static class MemoryPipe extends Thread implements AutoCloseable {
         private final FileDescriptor[] pipe;
diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
index a5f9305..eb4b315 100644
--- a/core/java/android/print/PrintFileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -118,7 +118,7 @@
         protected Void doInBackground(Void... params) {
             try (InputStream in = new FileInputStream(mFile);
                     OutputStream out = new FileOutputStream(mDestination.getFileDescriptor())) {
-                FileUtils.copy(in, out, null, mCancellationSignal);
+                FileUtils.copy(in, out, mCancellationSignal, null, null);
             } catch (OperationCanceledException e) {
                 // Ignored; already handled below
             } catch (IOException e) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a80dced..f97c64c 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -100,7 +100,8 @@
     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
 
     /** {@hide} */
-    public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+    @Deprecated
+    public static final String EXTRA_PACKAGE_NAME = Intent.EXTRA_PACKAGE_NAME;
 
     /** {@hide} */
     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 82b66d7..a6a6f35 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4161,7 +4161,8 @@
             NOTIFICATION_VIBRATION_INTENSITY,
             HAPTIC_FEEDBACK_INTENSITY,
             DISPLAY_COLOR_MODE,
-            ALARM_ALERT
+            ALARM_ALERT,
+            NOTIFICATION_LIGHT_PULSE,
         };
 
         /**
@@ -4364,6 +4365,7 @@
             VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
             VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
             VALIDATORS.put(SHOW_BATTERY_PERCENT, SHOW_BATTERY_PERCENT_VALIDATOR);
+            VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
         }
 
         /**
@@ -8019,6 +8021,8 @@
             MANUAL_RINGER_TOGGLE_COUNT,
             HUSH_GESTURE_USED,
             IN_CALL_NOTIFICATION_ENABLED,
+            LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+            LOCK_SCREEN_SHOW_NOTIFICATIONS,
         };
 
         /**
@@ -8161,6 +8165,8 @@
             VALIDATORS.put(HUSH_GESTURE_USED, HUSH_GESTURE_USED_VALIDATOR);
             VALIDATORS.put(MANUAL_RINGER_TOGGLE_COUNT, MANUAL_RINGER_TOGGLE_COUNT_VALIDATOR);
             VALIDATORS.put(IN_CALL_NOTIFICATION_ENABLED, IN_CALL_NOTIFICATION_ENABLED_VALIDATOR);
+            VALIDATORS.put(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+            VALIDATORS.put(LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         }
 
         /**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 7348cf6..0d94af4 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -65,6 +65,12 @@
     public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
 
     /**
+     * Data type: ArrayList of {@link android.app.Notification.Action}.
+     * Used to suggest extra actions for a notification.
+     */
+    public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index a7d70d0..09425a9 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1426,6 +1426,7 @@
         private boolean mShowBadge;
         private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
         private boolean mHidden;
+        private ArrayList<Notification.Action> mSmartActions;
 
         public Ranking() {}
 
@@ -1556,6 +1557,13 @@
         }
 
         /**
+         * @hide
+         */
+        public List<Notification.Action> getSmartActions() {
+            return mSmartActions;
+        }
+
+        /**
          * Returns whether this notification can be displayed as a badge.
          *
          * @return true if the notification can be displayed as a badge, false otherwise.
@@ -1583,7 +1591,7 @@
                 CharSequence explanation, String overrideGroupKey,
                 NotificationChannel channel, ArrayList<String> overridePeople,
                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
-                int userSentiment, boolean hidden) {
+                int userSentiment, boolean hidden, ArrayList<Notification.Action> smartActions) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1599,6 +1607,7 @@
             mShowBadge = showBadge;
             mUserSentiment = userSentiment;
             mHidden = hidden;
+            mSmartActions = smartActions;
         }
 
         /**
@@ -1648,6 +1657,7 @@
         private ArrayMap<String, Boolean> mShowBadge;
         private ArrayMap<String, Integer> mUserSentiment;
         private ArrayMap<String, Boolean> mHidden;
+        private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
@@ -1676,7 +1686,7 @@
                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
                     getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
-                    getShowBadge(key), getUserSentiment(key), getHidden(key));
+                    getShowBadge(key), getUserSentiment(key), getHidden(key), getSmartActions(key));
             return rank >= 0;
         }
 
@@ -1814,6 +1824,15 @@
             return hidden == null ? false : hidden.booleanValue();
         }
 
+        private ArrayList<Notification.Action> getSmartActions(String key) {
+            synchronized (this) {
+                if (mSmartActions == null) {
+                    buildSmartActions();
+                }
+            }
+            return mSmartActions.get(key);
+        }
+
         // Locked by 'this'
         private void buildRanksLocked() {
             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1931,6 +1950,15 @@
             }
         }
 
+        // Locked by 'this'
+        private void buildSmartActions() {
+            Bundle smartActions = mRankingUpdate.getSmartActions();
+            mSmartActions = new ArrayMap<>(smartActions.size());
+            for (String key : smartActions.keySet()) {
+                mSmartActions.put(key, smartActions.getParcelableArrayList(key));
+            }
+        }
+
         // ----------- Parcelable
 
         @Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 00c47ec..bed2214 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -37,12 +37,13 @@
     private final Bundle mShowBadge;
     private final Bundle mUserSentiment;
     private final Bundle mHidden;
+    private final Bundle mSmartActions;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
             Bundle visibilityOverrides, Bundle suppressedVisualEffects,
             int[] importance, Bundle explanation, Bundle overrideGroupKeys,
             Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
-            Bundle showBadge, Bundle userSentiment, Bundle hidden) {
+            Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions) {
         mKeys = keys;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
@@ -56,6 +57,7 @@
         mShowBadge = showBadge;
         mUserSentiment = userSentiment;
         mHidden = hidden;
+        mSmartActions = smartActions;
     }
 
     public NotificationRankingUpdate(Parcel in) {
@@ -73,6 +75,7 @@
         mShowBadge = in.readBundle();
         mUserSentiment = in.readBundle();
         mHidden = in.readBundle();
+        mSmartActions = in.readBundle();
     }
 
     @Override
@@ -95,6 +98,7 @@
         out.writeBundle(mShowBadge);
         out.writeBundle(mUserSentiment);
         out.writeBundle(mHidden);
+        out.writeBundle(mSmartActions);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -159,4 +163,8 @@
     public Bundle getHidden() {
         return mHidden;
     }
+
+    public Bundle getSmartActions() {
+        return mSmartActions;
+    }
 }
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 6b2f802..dde4c1d 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2091,6 +2091,25 @@
         return (T) text.subSequence(0, size);
     }
 
+    /**
+     * Trims the {@code text} to the first {@code size} characters and adds an ellipsis if the
+     * resulting string is shorter than the input. This will result in an output string which is
+     * longer than {@code size} for most inputs.
+     *
+     * @param size length of the result, should be greater than 0
+     *
+     * @hide
+     */
+    @Nullable
+    public static <T extends CharSequence> T trimToLengthWithEllipsis(@Nullable T text,
+            @IntRange(from = 1) int size) {
+        T trimmed = trimToSize(text, size);
+        if (trimmed.length() < text.length()) {
+            trimmed = (T) (trimmed.toString() + "...");
+        }
+        return trimmed;
+    }
+
     private static Object sLock = new Object();
 
     private static char[] sTemp = null;
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index f3d39de..08cbbe6 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -35,6 +35,7 @@
 import com.android.i18n.phonenumbers.PhoneNumberMatch;
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
+import com.android.internal.annotations.GuardedBy;
 
 import libcore.util.EmptyArray;
 
@@ -63,6 +64,10 @@
  *  does not have a URL scheme prefix, the supplied scheme will be prepended to
  *  create <code>http://example.com</code> when the clickable URL link is
  *  created.
+ *
+ * @see MatchFilter
+ * @see TransformFilter
+ * @see UrlSpanFactory
  */
 
 public class Linkify {
@@ -218,6 +223,44 @@
     }
 
     /**
+     * Factory class to create {@link URLSpan}s. While adding spans to a {@link Spannable},
+     * {@link Linkify} will call {@link UrlSpanFactory#create(String)} function to create a
+     * {@link URLSpan}.
+     *
+     * @see #addLinks(Spannable, int, UrlSpanFactory)
+     * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+     * UrlSpanFactory)
+     */
+    public static class UrlSpanFactory {
+        private static final Object sInstanceLock = new Object();
+
+        @GuardedBy("sInstanceLock")
+        private static volatile UrlSpanFactory sInstance = null;
+
+        private static synchronized UrlSpanFactory getInstance() {
+            if (sInstance == null) {
+                synchronized (sInstanceLock) {
+                    if (sInstance == null) {
+                        sInstance = new UrlSpanFactory();
+                    }
+                }
+            }
+            return sInstance;
+        }
+
+        /**
+         * Factory function that will called by {@link Linkify} in order to create a
+         * {@link URLSpan}.
+         *
+         * @param url URL found
+         * @return a URLSpan instance
+         */
+        public URLSpan create(final String url) {
+            return new URLSpan(url);
+        }
+    }
+
+    /**
      *  Scans the text of the provided Spannable and turns all occurrences
      *  of the link types indicated in the mask into clickable links.
      *  If the mask is nonzero, it also removes any existing URLSpans
@@ -228,24 +271,55 @@
      *  @param mask Mask to define which kinds of links will be searched.
      *
      *  @return True if at least one link is found and applied.
+     *
+     * @see #addLinks(Spannable, int, UrlSpanFactory)
      */
     public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) {
-        return addLinks(text, mask, null);
+        return addLinks(text, mask, null, null);
     }
 
+    /**
+     *  Scans the text of the provided Spannable and turns all occurrences
+     *  of the link types indicated in the mask into clickable links.
+     *  If the mask is nonzero, it also removes any existing URLSpans
+     *  attached to the Spannable, to avoid problems if you call it
+     *  repeatedly on the same text.
+     *
+     *  @param text Spannable whose text is to be marked-up with links
+     *  @param mask mask to define which kinds of links will be searched
+     *  @param urlSpanFactory factory class used to create {@link URLSpan}s
+     *  @return True if at least one link is found and applied.
+     */
+    public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
+            @Nullable UrlSpanFactory urlSpanFactory) {
+        return addLinks(text, mask, null, urlSpanFactory);
+    }
+
+    /**
+     *  Scans the text of the provided Spannable and turns all occurrences of the link types
+     *  indicated in the mask into clickable links. If the mask is nonzero, it also removes any
+     *  existing URLSpans attached to the Spannable, to avoid problems if you call it repeatedly
+     *  on the same text.
+     *
+     * @param text Spannable whose text is to be marked-up with links
+     * @param mask mask to define which kinds of links will be searched
+     * @param context Context to be used while identifying phone numbers
+     * @param urlSpanFactory factory class used to create {@link URLSpan}s
+     * @return true if at least one link is found and applied.
+     */
     private static boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
-            @Nullable Context context) {
+            @Nullable Context context, @Nullable UrlSpanFactory urlSpanFactory) {
         if (mask == 0) {
             return false;
         }
 
-        URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
+        final URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
 
         for (int i = old.length - 1; i >= 0; i--) {
             text.removeSpan(old[i]);
         }
 
-        ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
+        final ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
 
         if ((mask & WEB_URLS) != 0) {
             gatherLinks(links, text, Patterns.AUTOLINK_WEB_URL,
@@ -274,7 +348,7 @@
         }
 
         for (LinkSpec link: links) {
-            applyLink(link.url, link.start, link.end, text);
+            applyLink(link.url, link.start, link.end, text, urlSpanFactory);
         }
 
         return true;
@@ -290,6 +364,8 @@
      *  @param mask Mask to define which kinds of links will be searched.
      *
      *  @return True if at least one link is found and applied.
+     *
+     *  @see #addLinks(Spannable, int, UrlSpanFactory)
      */
     public static final boolean addLinks(@NonNull TextView text, @LinkifyMask int mask) {
         if (mask == 0) {
@@ -299,7 +375,7 @@
         final Context context = text.getContext();
         final CharSequence t = text.getText();
         if (t instanceof Spannable) {
-            if (addLinks((Spannable) t, mask, context)) {
+            if (addLinks((Spannable) t, mask, context, null)) {
                 addLinkMovementMethod(text);
                 return true;
             }
@@ -308,7 +384,7 @@
         } else {
             SpannableString s = SpannableString.valueOf(t);
 
-            if (addLinks(s, mask, context)) {
+            if (addLinks(s, mask, context, null)) {
                 addLinkMovementMethod(text);
                 text.setText(s);
 
@@ -403,6 +479,8 @@
      *  @param pattern      Regex pattern to be used for finding links
      *  @param scheme       URL scheme string (eg <code>http://</code>) to be
      *                      prepended to the links that do not start with this scheme.
+     * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+     * UrlSpanFactory)
      */
     public static final boolean addLinks(@NonNull Spannable text, @NonNull Pattern pattern,
             @Nullable String scheme) {
@@ -423,6 +501,8 @@
      * @param transformFilter Filter to allow the client code to update the link found.
      *
      * @return True if at least one link is found and applied.
+     * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+     * UrlSpanFactory)
      */
     public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
             @Nullable String scheme, @Nullable MatchFilter matchFilter,
@@ -446,10 +526,39 @@
      * @param transformFilter Filter to allow the client code to update the link found.
      *
      * @return True if at least one link is found and applied.
+     *
+     * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+     * UrlSpanFactory)
      */
     public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
-            @Nullable  String defaultScheme, @Nullable String[] schemes,
+            @Nullable String defaultScheme, @Nullable String[] schemes,
             @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter) {
+        return addLinks(spannable, pattern, defaultScheme, schemes, matchFilter, transformFilter,
+                null);
+    }
+
+    /**
+     * Applies a regex to a Spannable turning the matches into links.
+     *
+     * @param spannable       spannable whose text is to be marked-up with links.
+     * @param pattern         regex pattern to be used for finding links.
+     * @param defaultScheme   the default scheme to be prepended to links if the link does not
+     *                        start with one of the <code>schemes</code> given.
+     * @param schemes         array of schemes (eg <code>http://</code>) to check if the link found
+     *                        contains a scheme. Passing a null or empty value means prepend
+     *                        defaultScheme
+     *                        to all links.
+     * @param matchFilter     the filter that is used to allow the client code additional control
+     *                        over which pattern matches are to be converted into links.
+     * @param transformFilter filter to allow the client code to update the link found.
+     * @param urlSpanFactory  factory class used to create {@link URLSpan}s
+     *
+     * @return True if at least one link is found and applied.
+     */
+    public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
+            @Nullable String defaultScheme, @Nullable String[] schemes,
+            @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter,
+            @Nullable UrlSpanFactory urlSpanFactory) {
         final String[] schemesCopy;
         if (defaultScheme == null) defaultScheme = "";
         if (schemes == null || schemes.length < 1) {
@@ -478,7 +587,7 @@
             if (allowed) {
                 String url = makeUrl(m.group(0), schemesCopy, m, transformFilter);
 
-                applyLink(url, start, end, spannable);
+                applyLink(url, start, end, spannable, urlSpanFactory);
                 hasMatches = true;
             }
         }
@@ -486,9 +595,12 @@
         return hasMatches;
     }
 
-    private static final void applyLink(String url, int start, int end, Spannable text) {
-        URLSpan span = new URLSpan(url);
-
+    private static void applyLink(String url, int start, int end, Spannable text,
+            @Nullable UrlSpanFactory urlSpanFactory) {
+        if (urlSpanFactory == null) {
+            urlSpanFactory = UrlSpanFactory.getInstance();
+        }
+        final URLSpan span = urlSpanFactory.create(url);
         text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
     }
 
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index b2e24c3..72865cc 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -187,6 +187,21 @@
     }
 
     /**
+     * Perform Hermite interpolation between two values.
+     * Eg:
+     *   smoothStep(0, 0.5f, 0.5f) = 1f
+     *   smoothStep(0, 0.5f, 0.25f) = 0.5f
+     *
+     * @param start Left edge.
+     * @param end Right edge.
+     * @param x A value between {@code start} and {@code end}.
+     * @return A number between 0 and 1 representing where {@code x} is in the interpolation.
+     */
+    public static float smoothStep(float start, float end, float x) {
+        return constrain((x - start) / (end - start), 0f, 1f);
+    }
+
+    /**
      * Returns the sum of the two parameters, or throws an exception if the resulting sum would
      * cause an overflow or underflow.
      * @throws IllegalArgumentException when overflow or underflow would occur.
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index 041e8a8..e3b8fec 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -16,27 +16,27 @@
 
 package android.util;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import org.apache.harmony.xml.ExpatReader;
-import org.kxml2.io.KXmlParser;
+import libcore.util.XmlObjectFactory;
+
 import org.xml.sax.ContentHandler;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 import org.xml.sax.XMLReader;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
 /**
  * XML utility methods.
  */
 public class Xml {
-    /** @hide */ public Xml() {}
+    private Xml() {}
 
     /**
      * {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name.
@@ -52,7 +52,7 @@
     public static void parse(String xml, ContentHandler contentHandler)
             throws SAXException {
         try {
-            XMLReader reader = new ExpatReader();
+            XMLReader reader = XmlObjectFactory.newXMLReader();
             reader.setContentHandler(contentHandler);
             reader.parse(new InputSource(new StringReader(xml)));
         } catch (IOException e) {
@@ -66,7 +66,7 @@
      */
     public static void parse(Reader in, ContentHandler contentHandler)
             throws IOException, SAXException {
-        XMLReader reader = new ExpatReader();
+        XMLReader reader = XmlObjectFactory.newXMLReader();
         reader.setContentHandler(contentHandler);
         reader.parse(new InputSource(in));
     }
@@ -77,7 +77,7 @@
      */
     public static void parse(InputStream in, Encoding encoding,
             ContentHandler contentHandler) throws IOException, SAXException {
-        XMLReader reader = new ExpatReader();
+        XMLReader reader = XmlObjectFactory.newXMLReader();
         reader.setContentHandler(contentHandler);
         InputSource source = new InputSource(in);
         source.setEncoding(encoding.expatName);
@@ -89,7 +89,7 @@
      */
     public static XmlPullParser newPullParser() {
         try {
-            KXmlParser parser = new KXmlParser();
+            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
             return parser;
@@ -102,25 +102,7 @@
      * Creates a new xml serializer.
      */
     public static XmlSerializer newSerializer() {
-        try {
-            return XmlSerializerFactory.instance.newSerializer();
-        } catch (XmlPullParserException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    /** Factory for xml serializers. Initialized on demand. */
-    static class XmlSerializerFactory {
-        static final String TYPE
-                = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
-        static final XmlPullParserFactory instance;
-        static {
-            try {
-                instance = XmlPullParserFactory.newInstance(TYPE, null);
-            } catch (XmlPullParserException e) {
-                throw new AssertionError(e);
-            }
-        }
+        return XmlObjectFactory.newXmlSerializer();
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 19e95b8..e2e2b00 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7547,7 +7547,7 @@
      */
     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
         if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
-                && !TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+                && isAccessibilityPane()) {
             event.getText().add(getAccessibilityPaneTitle());
         }
     }
@@ -12963,7 +12963,7 @@
                 }
             }
         }
-        if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+        if (isAccessibilityPane()) {
             if (isVisible != oldVisible) {
                 notifyViewAccessibilityStateChangedIfNeeded(isVisible
                         ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e8e6537..eee3630 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -601,22 +601,14 @@
 
     private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
 
-    private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
-
     private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
 
     private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
 
-    private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
-
-    private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
-
     private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
 
     private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
 
-    private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
-
     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
 
     private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
@@ -631,8 +623,6 @@
 
     private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
 
-    private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
-
     private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
 
     private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
@@ -1168,6 +1158,10 @@
         mActions.add(action);
     }
 
+    private boolean hasActionWithId(int actionId) {
+        return getActionList().stream().anyMatch(action -> action.getId() == actionId);
+    }
+
     /**
      * Adds an action that can be performed on the node.
      * <p>
@@ -1767,7 +1761,7 @@
      * @return True if the node is focusable.
      */
     public boolean isFocusable() {
-        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
+        return hasActionWithId(ACTION_FOCUS);
     }
 
     /**
@@ -1781,10 +1775,11 @@
      * @param focusable True if the node is focusable.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Use {@link #addAction(AccessibilityAction)}
+     * with {@link AccessibilityAction#ACTION_FOCUS}
      */
-    public void setFocusable(boolean focusable) {
-        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
-    }
+    @Deprecated
+    public void setFocusable(boolean focusable) { }
 
     /**
      * Gets whether this node is focused.
@@ -1892,7 +1887,7 @@
      * @return True if the node is clickable.
      */
     public boolean isClickable() {
-        return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
+        return hasActionWithId(ACTION_CLICK);
     }
 
     /**
@@ -1906,10 +1901,11 @@
      * @param clickable True if the node is clickable.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Use {@link #addAction(AccessibilityAction)}
+     * with {@link AccessibilityAction#ACTION_CLICK}
      */
-    public void setClickable(boolean clickable) {
-        setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
-    }
+    @Deprecated
+    public void setClickable(boolean clickable) { }
 
     /**
      * Gets whether this node is long clickable.
@@ -1917,7 +1913,7 @@
      * @return True if the node is long clickable.
      */
     public boolean isLongClickable() {
-        return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
+        return hasActionWithId(ACTION_LONG_CLICK);
     }
 
     /**
@@ -1931,10 +1927,11 @@
      * @param longClickable True if the node is long clickable.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Use {@link #addAction(AccessibilityAction)}
+     * with {@link AccessibilityAction#ACTION_LONG_CLICK}
      */
-    public void setLongClickable(boolean longClickable) {
-        setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
-    }
+    @Deprecated
+    public void setLongClickable(boolean longClickable) { }
 
     /**
      * Gets whether this node is enabled.
@@ -1992,7 +1989,13 @@
      * @return True if the node is scrollable, false otherwise.
      */
     public boolean isScrollable() {
-        return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
+        return hasActionWithId(ACTION_SCROLL_BACKWARD)
+                || hasActionWithId(ACTION_SCROLL_FORWARD)
+                || hasActionWithId(R.id.accessibilityActionScrollToPosition)
+                || hasActionWithId(R.id.accessibilityActionScrollUp)
+                || hasActionWithId(R.id.accessibilityActionScrollDown)
+                || hasActionWithId(R.id.accessibilityActionScrollLeft)
+                || hasActionWithId(R.id.accessibilityActionScrollRight);
     }
 
     /**
@@ -2006,9 +2009,11 @@
      * @param scrollable True if the node is scrollable, false otherwise.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Use {@link #addAction(AccessibilityAction)}
      */
+    @Deprecated
+
     public void setScrollable(boolean scrollable) {
-        setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
     }
 
     /**
@@ -2199,7 +2204,7 @@
      * @return True if the node is context clickable.
      */
     public boolean isContextClickable() {
-        return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
+        return hasActionWithId(R.id.accessibilityActionContextClick);
     }
 
     /**
@@ -2212,10 +2217,11 @@
      *
      * @param contextClickable True if the node is context clickable.
      * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Use {@link #addAction(AccessibilityAction)}
+     * with {@link AccessibilityAction#ACTION_CONTEXT_CLICK}
      */
-    public void setContextClickable(boolean contextClickable) {
-        setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
-    }
+    @Deprecated
+    public void setContextClickable(boolean contextClickable) { }
 
     /**
      * Gets the node's live region mode.
@@ -2309,7 +2315,7 @@
      * @return If the node can be dismissed.
      */
     public boolean isDismissable() {
-        return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
+        return hasActionWithId(ACTION_DISMISS);
     }
 
     /**
@@ -2321,10 +2327,11 @@
      * </p>
      *
      * @param dismissable If the node can be dismissed.
+     * @deprecated Use {@link #addAction(AccessibilityAction)}
+     * with {@link AccessibilityAction#ACTION_DISMISS}
      */
-    public void setDismissable(boolean dismissable) {
-        setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
-    }
+    @Deprecated
+    public void setDismissable(boolean dismissable) { }
 
     /**
      * Returns whether the node originates from a view considered important for accessibility.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 8f28102..41daf9e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -57,6 +57,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
@@ -72,6 +73,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 //TODO: use java.lang.ref.Cleaner once Android supports Java 9
 import sun.misc.Cleaner;
@@ -572,10 +575,11 @@
 
                 final AutofillClient client = getClient();
                 if (client != null) {
+                    final SyncResultReceiver receiver = new SyncResultReceiver();
                     try {
-                        final boolean sessionWasRestored = mService.restoreSession(mSessionId,
-                                client.autofillClientGetActivityToken(),
-                                mServiceClient.asBinder());
+                        mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(),
+                                mServiceClient.asBinder(), receiver);
+                        final boolean sessionWasRestored = receiver.getIntResult() == 1;
 
                         if (!sessionWasRestored) {
                             Log.w(TAG, "Session " + mSessionId + " could not be restored");
@@ -691,7 +695,9 @@
      */
     @Nullable public FillEventHistory getFillEventHistory() {
         try {
-            return mService.getFillEventHistory();
+            final SyncResultReceiver receiver = new SyncResultReceiver();
+            mService.getFillEventHistory(receiver);
+            return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return null;
@@ -1242,8 +1248,10 @@
     public boolean hasEnabledAutofillServices() {
         if (mService == null) return false;
 
+        final SyncResultReceiver receiver = new SyncResultReceiver();
         try {
-            return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
+            mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver);
+            return receiver.getIntResult() == 1;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1257,8 +1265,10 @@
     public ComponentName getAutofillServiceComponentName() {
         if (mService == null) return null;
 
+        final SyncResultReceiver receiver = new SyncResultReceiver();
         try {
-            return mService.getAutofillServiceComponentName();
+            mService.getAutofillServiceComponentName(receiver);
+            return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1281,7 +1291,9 @@
      */
     @Nullable public String getUserDataId() {
         try {
-            return mService.getUserDataId();
+            final SyncResultReceiver receiver = new SyncResultReceiver();
+            mService.getUserDataId(receiver);
+            return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return null;
@@ -1301,7 +1313,9 @@
      */
     @Nullable public UserData getUserData() {
         try {
-            return mService.getUserData();
+            final SyncResultReceiver receiver = new SyncResultReceiver();
+            mService.getUserData(receiver);
+            return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return null;
@@ -1337,8 +1351,10 @@
      * the user.
      */
     public boolean isFieldClassificationEnabled() {
+        final SyncResultReceiver receiver = new SyncResultReceiver();
         try {
-            return mService.isFieldClassificationEnabled();
+            mService.isFieldClassificationEnabled(receiver);
+            return receiver.getIntResult() == 1;
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return false;
@@ -1358,8 +1374,10 @@
      */
     @Nullable
     public String getDefaultFieldClassificationAlgorithm() {
+        final SyncResultReceiver receiver = new SyncResultReceiver();
         try {
-            return mService.getDefaultFieldClassificationAlgorithm();
+            mService.getDefaultFieldClassificationAlgorithm(receiver);
+            return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return null;
@@ -1376,9 +1394,10 @@
      */
     @NonNull
     public List<String> getAvailableFieldClassificationAlgorithms() {
-        final String[] algorithms;
+        final SyncResultReceiver receiver = new SyncResultReceiver();
         try {
-            algorithms = mService.getAvailableFieldClassificationAlgorithms();
+            mService.getAvailableFieldClassificationAlgorithms(receiver);
+            final String[] algorithms = receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
             return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -1399,8 +1418,10 @@
     public boolean isAutofillSupported() {
         if (mService == null) return false;
 
+        final SyncResultReceiver receiver = new SyncResultReceiver();
         try {
-            return mService.isServiceSupported(mContext.getUserId());
+            mService.isServiceSupported(mContext.getUserId(), receiver);
+            return receiver.getIntResult() == 1;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1521,10 +1542,12 @@
             final AutofillClient client = getClient();
             if (client == null) return; // NOTE: getClient() already logged it..
 
-            mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
+            final SyncResultReceiver receiver = new SyncResultReceiver();
+            mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, client.autofillClientGetComponentName(),
-                    isCompatibilityModeEnabledLocked());
+                    isCompatibilityModeEnabledLocked(), receiver);
+            mSessionId = receiver.getIntResult();
             if (mSessionId != NO_SESSION) {
                 mState = STATE_ACTIVE;
             }
@@ -1602,7 +1625,9 @@
             mServiceClient = new AutofillManagerClient(this);
             try {
                 final int userId = mContext.getUserId();
-                final int flags = mService.addClient(mServiceClient, userId);
+                final SyncResultReceiver receiver = new SyncResultReceiver();
+                mService.addClient(mServiceClient, userId, receiver);
+                final int flags = receiver.getIntResult();
                 mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
                 sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
                 sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
@@ -1923,7 +1948,7 @@
                         mFillableIds.add(id);
                     }
                     if (sVerbose) {
-                        Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
+                        Log.v(TAG, "setTrackedViews(): fillableIds=" + Arrays.toString(fillableIds)
                                 + ", mFillableIds" + mFillableIds);
                     }
                 }
@@ -2818,4 +2843,104 @@
             }
         }
     }
+
+    /**
+     * @hide
+     */
+    public static final class SyncResultReceiver extends IResultReceiver.Stub {
+
+        private static final String EXTRA = "EXTRA";
+
+        /**
+         * How long to block waiting for {@link IResultReceiver} callbacks when calling server.
+         */
+        private static final long BINDER_TIMEOUT_MS = 5000;
+
+        private static final int TYPE_STRING = 0;
+        private static final int TYPE_STRING_ARRAY = 1;
+        private static final int TYPE_PARCELABLE = 2;
+
+        private final CountDownLatch mLatch  = new CountDownLatch(1);
+        private int mResult;
+        private Bundle mBundle;
+
+        private void waitResult() {
+            try {
+                if (!mLatch.await(BINDER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    throw new IllegalStateException("Not called in " + BINDER_TIMEOUT_MS + "ms");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        /**
+         * Gets the result from an operation that returns an {@code int}.
+         */
+        int getIntResult() {
+            waitResult();
+            return mResult;
+        }
+
+        /**
+         * Gets the result from an operation that returns an {@code Object}.
+         *
+         * @param type type of expected object.
+         */
+        @Nullable
+        @SuppressWarnings("unchecked")
+        <T> T getObjectResult(int type) {
+            waitResult();
+            if (mBundle == null) {
+                return null;
+            }
+            switch (type) {
+                case TYPE_STRING:
+                    return (T) mBundle.getString(EXTRA);
+                case TYPE_STRING_ARRAY:
+                    return (T) mBundle.getString(EXTRA);
+                case TYPE_PARCELABLE:
+                    return (T) mBundle.getParcelable(EXTRA);
+                default:
+                    throw new IllegalArgumentException("unsupported type: " + type);
+            }
+        }
+
+        @Override
+        public void send(int resultCode, Bundle resultData) {
+            mResult = resultCode;
+            mBundle = resultData;
+            mLatch.countDown();
+        }
+
+        /**
+         * Creates a bundle for a {@code String} value.
+         */
+        @NonNull
+        public static Bundle bundleFor(@Nullable String value) {
+            final Bundle bundle = new Bundle();
+            bundle.putString(EXTRA, value);
+            return bundle;
+        }
+
+        /**
+         * Creates a bundle for a {@code String[]} value.
+         */
+        @NonNull
+        public static Bundle bundleFor(@Nullable String[] value) {
+            final Bundle bundle = new Bundle();
+            bundle.putStringArray(EXTRA, value);
+            return bundle;
+        }
+
+        /**
+         * Creates a bundle for a {@code Parcelable} value.
+         */
+        @NonNull
+        public static Bundle bundleFor(@Nullable Parcelable value) {
+            final Bundle bundle = new Bundle();
+            bundle.putParcelable(EXTRA, value);
+            return bundle;
+        }
+    }
 }
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 6b26f23..26aeba5 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -28,41 +28,39 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
+import com.android.internal.os.IResultReceiver;
 
 /**
  * Mediator between apps being auto-filled and auto-fill service implementations.
  *
  * {@hide}
  */
- // TODO(b/73536867) STOPSHIP : this whole interface should be either oneway or not, and we're
- // gradually converting the methods (as some of them return a value form the server and must be
- // refactored).
-interface IAutoFillManager {
+oneway interface IAutoFillManager {
     // Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE
-    int addClient(in IAutoFillManagerClient client, int userId);
+    void addClient(in IAutoFillManagerClient client, int userId, in IResultReceiver result);
     void removeClient(in IAutoFillManagerClient client, int userId);
-    int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
-            in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
-            in ComponentName componentName, boolean compatMode);
-    FillEventHistory getFillEventHistory();
-    boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
-    oneway void updateSession(int sessionId, in AutofillId id, in Rect bounds,
-            in AutofillValue value, int action, int flags, int userId);
-    oneway void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
-    oneway void finishSession(int sessionId, int userId);
-    oneway void cancelSession(int sessionId, int userId);
-    oneway void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId,
-            int userId);
-    oneway void setHasCallback(int sessionId, int userId, boolean hasIt);
+    void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
+        in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
+        in ComponentName componentName, boolean compatMode, in IResultReceiver result);
+    void getFillEventHistory(in IResultReceiver result);
+    void restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback,
+        in IResultReceiver result);
+    void updateSession(int sessionId, in AutofillId id, in Rect bounds,
+        in AutofillValue value, int action, int flags, int userId);
+    void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+    void finishSession(int sessionId, int userId);
+    void cancelSession(int sessionId, int userId);
+    void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
+    void setHasCallback(int sessionId, int userId, boolean hasIt);
     void disableOwnedAutofillServices(int userId);
-    boolean isServiceSupported(int userId);
-    boolean isServiceEnabled(int userId, String packageName);
+    void isServiceSupported(int userId, in IResultReceiver result);
+    void isServiceEnabled(int userId, String packageName, in IResultReceiver result);
     void onPendingSaveUi(int operation, IBinder token);
-    UserData getUserData();
-    String getUserDataId();
+    void getUserData(in IResultReceiver result);
+    void getUserDataId(in IResultReceiver result);
     void setUserData(in UserData userData);
-    boolean isFieldClassificationEnabled();
-    ComponentName getAutofillServiceComponentName();
-    String[] getAvailableFieldClassificationAlgorithms();
-    String getDefaultFieldClassificationAlgorithm();
+    void isFieldClassificationEnabled(in IResultReceiver result);
+    void getAutofillServiceComponentName(in IResultReceiver result);
+    void getAvailableFieldClassificationAlgorithms(in IResultReceiver result);
+    void getDefaultFieldClassificationAlgorithm(in IResultReceiver result);
 }
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index dbf7c93..ad42288 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -129,7 +129,7 @@
     private final PssTable mPssTable;
 
     private ProcessState mCommonProcess;
-    private int mCurState = STATE_NOTHING;
+    private int mCurCombinedState = STATE_NOTHING;
     private long mStartTime;
 
     private int mLastPssState = STATE_NOTHING;
@@ -180,7 +180,7 @@
         mPackage = pkg;
         mUid = uid;
         mVersion = vers;
-        mCurState = commonProcess.mCurState;
+        mCurCombinedState = commonProcess.mCurCombinedState;
         mStartTime = now;
         mDurations = new DurationsTable(commonProcess.mStats.mTableData);
         mPssTable = new PssTable(commonProcess.mStats.mTableData);
@@ -324,7 +324,7 @@
 
     public boolean isInUse() {
         return mActive || mNumActiveServices > 0 || mNumStartedServices > 0
-                || mCurState != STATE_NOTHING;
+                || mCurCombinedState != STATE_NOTHING;
     }
 
     public boolean isActive() {
@@ -333,7 +333,7 @@
 
     public boolean hasAnyData() {
         return !(mDurations.getKeyCount() == 0
-                && mCurState == STATE_NOTHING
+                && mCurCombinedState == STATE_NOTHING
                 && mPssTable.getKeyCount() == 0);
     }
 
@@ -355,7 +355,7 @@
         }
 
         // First update the common process.
-        mCommonProcess.setState(state, now);
+        mCommonProcess.setCombinedState(state, now);
 
         // If the common process is not multi-package, there is nothing else to do.
         if (!mCommonProcess.mMultiPackage) {
@@ -364,29 +364,29 @@
 
         if (pkgList != null) {
             for (int ip=pkgList.size()-1; ip>=0; ip--) {
-                pullFixedProc(pkgList, ip).setState(state, now);
+                pullFixedProc(pkgList, ip).setCombinedState(state, now);
             }
         }
     }
 
-    public void setState(int state, long now) {
+    public void setCombinedState(int state, long now) {
         ensureNotDead();
-        if (!mDead && (mCurState != state)) {
+        if (!mDead && (mCurCombinedState != state)) {
             //Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
             commitStateTime(now);
-            mCurState = state;
+            mCurCombinedState = state;
         }
     }
 
-    public int getState() {
-        return mCurState;
+    public int getCombinedState() {
+        return mCurCombinedState;
     }
 
     public void commitStateTime(long now) {
-        if (mCurState != STATE_NOTHING) {
+        if (mCurCombinedState != STATE_NOTHING) {
             long dur = now - mStartTime;
             if (dur > 0) {
-                mDurations.addDuration(mCurState, dur);
+                mDurations.addDuration(mCurCombinedState, dur);
             }
         }
         mStartTime = now;
@@ -434,8 +434,8 @@
             mCommonProcess.incStartedServices(memFactor, now, serviceName);
         }
         mNumStartedServices++;
-        if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) {
-            setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+        if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
+            setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
         }
     }
 
@@ -450,8 +450,8 @@
             mCommonProcess.decStartedServices(memFactor, now, serviceName);
         }
         mNumStartedServices--;
-        if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) {
-            setState(STATE_NOTHING, now);
+        if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
+            setCombinedState(STATE_NOTHING, now);
         } else if (mNumStartedServices < 0) {
             Slog.wtfStack(TAG, "Proc started services underrun: pkg="
                     + mPackage + " uid=" + mUid + " name=" + mName);
@@ -485,16 +485,16 @@
                 break;
         }
         if (!always) {
-            if (mLastPssState == mCurState && SystemClock.uptimeMillis()
+            if (mLastPssState == mCurCombinedState && SystemClock.uptimeMillis()
                     < (mLastPssTime+(30*1000))) {
                 return;
             }
         }
-        mLastPssState = mCurState;
+        mLastPssState = mCurCombinedState;
         mLastPssTime = SystemClock.uptimeMillis();
-        if (mCurState != STATE_NOTHING) {
+        if (mCurCombinedState != STATE_NOTHING) {
             // First update the common process.
-            mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss,
+            mCommonProcess.mPssTable.mergeStats(mCurCombinedState, 1, pss, pss, pss, uss, uss, uss,
                     rss, rss, rss);
 
             // If the common process is not multi-package, there is nothing else to do.
@@ -504,7 +504,7 @@
 
             if (pkgList != null) {
                 for (int ip=pkgList.size()-1; ip>=0; ip--) {
-                    pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurState, 1,
+                    pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurCombinedState, 1,
                             pss, pss, pss, uss, uss, uss, rss, rss, rss);
                 }
             }
@@ -623,7 +623,7 @@
 
     public long getDuration(int state, long now) {
         long time = mDurations.getValueForId((byte)state);
-        if (mCurState == state) {
+        if (mCurCombinedState == state) {
             time += now - mStartTime;
         }
         return time;
@@ -728,7 +728,7 @@
             final int key = mDurations.getKeyAt(i);
             final int type = SparseMappingTable.getIdFromKey(key);
             long time = mDurations.getValue(key);
-            if (mCurState == type) {
+            if (mCurCombinedState == type) {
                 time += now - mStartTime;
             }
             final int procState = type % STATE_COUNT;
@@ -831,7 +831,7 @@
                     final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
                     long time = mDurations.getValueForId((byte)bucket);
                     String running = "";
-                    if (mCurState == bucket) {
+                    if (mCurCombinedState == bucket) {
                         running = " (running)";
                     }
                     if (time != 0) {
@@ -1181,14 +1181,14 @@
             final int key = mDurations.getKeyAt(i);
             final int type = SparseMappingTable.getIdFromKey(key);
             long time = mDurations.getValue(key);
-            if (mCurState == type) {
+            if (mCurCombinedState == type) {
                 didCurState = true;
                 time += now - mStartTime;
             }
             DumpUtils.printProcStateTagAndValue(pw, type, time);
         }
-        if (!didCurState && mCurState != STATE_NOTHING) {
-            DumpUtils.printProcStateTagAndValue(pw, mCurState, now - mStartTime);
+        if (!didCurState && mCurCombinedState != STATE_NOTHING) {
+            DumpUtils.printProcStateTagAndValue(pw, mCurCombinedState, now - mStartTime);
         }
     }
 
@@ -1255,14 +1255,14 @@
             final int key = mDurations.getKeyAt(i);
             final int type = SparseMappingTable.getIdFromKey(key);
             long time = mDurations.getValue(key);
-            if (mCurState == type) {
+            if (mCurCombinedState == type) {
                 didCurState = true;
                 time += now - mStartTime;
             }
             durationByState.put(type, time);
         }
-        if (!didCurState && mCurState != STATE_NOTHING) {
-            durationByState.put(mCurState, now - mStartTime);
+        if (!didCurState && mCurCombinedState != STATE_NOTHING) {
+            durationByState.put(mCurCombinedState, now - mStartTime);
         }
 
         for (int i=0; i<mPssTable.getKeyCount(); i++) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 3cafa5e..15f140e 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -1387,14 +1387,15 @@
             } else {
                 final ProcessState proc = act.getAssociationState().getProcess();
                 if (proc != null) {
-                    if (act.mProcState == proc.getState()) {
+                    final int procState = proc.getCombinedState() % STATE_COUNT;
+                    if (act.mProcState == procState) {
                         act.startActive(now);
                     } else {
                         act.stopActive(now);
-                        if (act.mProcState < proc.getState()) {
+                        if (act.mProcState < procState) {
                             Slog.w(TAG, "Tracking association " + act + " whose proc state "
                                     + act.mProcState + " is better than process " + proc
-                                    + " proc state " + proc.getState());
+                                    + " proc state " + procState);
                         }
                     }
                 } else {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 20eab92..f87c081 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.os;
 
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -23,6 +24,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -75,10 +77,10 @@
     }
 
     public CallSession callStarted(Binder binder, int code) {
-        return callStarted(binder.getClass().getName(), code);
+        return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code));
     }
 
-    private CallSession callStarted(String className, int code) {
+    private CallSession callStarted(String className, int code, @Nullable String methodName) {
         if (!mEnabled) {
           return NOT_ENABLED;
         }
@@ -90,6 +92,7 @@
 
         s.callStat.className = className;
         s.callStat.msg = code;
+        s.callStat.methodName = methodName;
         s.exceptionThrown = false;
         s.cpuTimeStarted = -1;
         s.timeStarted = -1;
@@ -169,6 +172,7 @@
                 callStat = s.sampledCallStat;
             }
             callStat.callCount++;
+            callStat.methodName = s.callStat.methodName;
             if (s.cpuTimeStarted >= 0) {
                 callStat.cpuTimeMicros += duration;
                 callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
@@ -209,6 +213,39 @@
         }
     }
 
+    public ArrayList<ExportedCallStat> getExportedCallStats() {
+        // We do not collect all the data if detailed tracking is off.
+        if (!mDetailedTracking) {
+          return new ArrayList<ExportedCallStat>();
+        }
+
+        ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+        synchronized (mLock) {
+            int uidEntriesSize = mUidEntries.size();
+            for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++){
+                UidEntry entry = mUidEntries.valueAt(entryIdx);
+                for (CallStat stat : entry.getCallStatsList()) {
+                    ExportedCallStat exported = new ExportedCallStat();
+                    exported.uid = entry.uid;
+                    exported.className = stat.className;
+                    exported.methodName = stat.methodName == null
+                        ? String.valueOf(stat.msg) : stat.methodName;
+                    exported.cpuTimeMicros = stat.cpuTimeMicros;
+                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
+                    exported.latencyMicros = stat.latencyMicros;
+                    exported.maxLatencyMicros = stat.maxLatencyMicros;
+                    exported.callCount = stat.callCount;
+                    exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
+                    exported.maxReplySizeBytes = stat.maxReplySizeBytes;
+                    exported.exceptionCount = stat.exceptionCount;
+                    resultCallStats.add(exported);
+                }
+            }
+        }
+
+        return resultCallStats;
+    }
+
     public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
         synchronized (mLock) {
             dumpLocked(pw, appIdToPkgNameMap, verbose);
@@ -250,6 +287,7 @@
                     sb.setLength(0);
                     sb.append("    ")
                             .append(uidToString(uidEntry.uid, appIdToPkgNameMap))
+                            .append(",").append(e)
                             .append(',').append(e.cpuTimeMicros)
                             .append(',').append(e.maxCpuTimeMicros)
                             .append(',').append(e.latencyMicros)
@@ -368,10 +406,30 @@
         }
     }
 
+    /**
+     * Aggregated data by uid/class/method to be sent through WestWorld.
+     */
+    public static class ExportedCallStat {
+        public int uid;
+        public String className;
+        public String methodName;
+        public long cpuTimeMicros;
+        public long maxCpuTimeMicros;
+        public long latencyMicros;
+        public long maxLatencyMicros;
+        public long callCount;
+        public long maxRequestSizeBytes;
+        public long maxReplySizeBytes;
+        public long exceptionCount;
+    }
+
     @VisibleForTesting
     public static class CallStat {
         public String className;
         public int msg;
+        // Method name might be null when we cannot resolve the transaction code. For instance, if
+        // the binder was not generated by AIDL.
+        public @Nullable String methodName;
         public long cpuTimeMicros;
         public long maxCpuTimeMicros;
         public long latencyMicros;
@@ -410,7 +468,7 @@
 
         @Override
         public String toString() {
-            return className + "/" + msg;
+            return className + "#" + (methodName == null ? msg : methodName);
         }
     }
 
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 60dd86b..16ca4fc 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -454,9 +454,12 @@
     /**
      * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
      */
-    public static int resolveColor(Context context, int color) {
+    public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
         if (color == Notification.COLOR_DEFAULT) {
-            return context.getColor(com.android.internal.R.color.notification_default_color_light);
+            int res = defaultBackgroundIsDark
+                    ? com.android.internal.R.color.notification_default_color_dark
+                    : com.android.internal.R.color.notification_default_color_light;
+            return context.getColor(res);
         }
         return color;
     }
@@ -486,7 +489,7 @@
      */
     public static int resolveContrastColor(Context context, int notificationColor,
             int backgroundColor, boolean isDark) {
-        final int resolvedColor = resolveColor(context, notificationColor);
+        final int resolvedColor = resolveColor(context, notificationColor, isDark);
 
         int color = resolvedColor;
         color = ContrastColorUtil.ensureTextContrast(color, backgroundColor, isDark);
@@ -520,7 +523,8 @@
     }
 
     public static int resolveAmbientColor(Context context, int notificationColor) {
-        final int resolvedColor = resolveColor(context, notificationColor);
+        final int resolvedColor = resolveColor(context, notificationColor,
+                true /* defaultBackgroundIsDark */);
 
         int color = resolvedColor;
         color = ContrastColorUtil.ensureTextContrastOnBlack(color);
@@ -538,8 +542,9 @@
         return color;
     }
 
-    public static int resolvePrimaryColor(Context context, int backgroundColor) {
-        boolean useDark = shouldUseDark(backgroundColor);
+    public static int resolvePrimaryColor(Context context, int backgroundColor,
+                                          boolean defaultBackgroundIsDark) {
+        boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
         if (useDark) {
             return context.getColor(
                     com.android.internal.R.color.notification_primary_text_color_light);
@@ -549,8 +554,9 @@
         }
     }
 
-    public static int resolveSecondaryColor(Context context, int backgroundColor) {
-        boolean useDark = shouldUseDark(backgroundColor);
+    public static int resolveSecondaryColor(Context context, int backgroundColor,
+                                            boolean defaultBackgroundIsDark) {
+        boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
         if (useDark) {
             return context.getColor(
                     com.android.internal.R.color.notification_secondary_text_color_light);
@@ -560,8 +566,9 @@
         }
     }
 
-    public static int resolveDefaultColor(Context context, int backgroundColor) {
-        boolean useDark = shouldUseDark(backgroundColor);
+    public static int resolveDefaultColor(Context context, int backgroundColor,
+                                          boolean defaultBackgroundIsDark) {
+        boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
         if (useDark) {
             return context.getColor(
                     com.android.internal.R.color.notification_default_color_light);
@@ -591,12 +598,11 @@
         return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
     }
 
-    private static boolean shouldUseDark(int backgroundColor) {
-        boolean useDark = backgroundColor == Notification.COLOR_DEFAULT;
-        if (!useDark) {
-            useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
+    private static boolean shouldUseDark(int backgroundColor, boolean defaultBackgroundIsDark) {
+        if (backgroundColor == Notification.COLOR_DEFAULT) {
+            return !defaultBackgroundIsDark;
         }
-        return useDark;
+        return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
     }
 
     public static double calculateLuminance(int backgroundColor) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d4903d8..b675698 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -126,7 +126,6 @@
         "android/graphics/Camera.cpp",
         "android/graphics/CanvasProperty.cpp",
         "android/graphics/ColorFilter.cpp",
-        "android/graphics/DrawFilter.cpp",
         "android/graphics/FontFamily.cpp",
         "android/graphics/FontUtils.cpp",
         "android/graphics/CreateJavaOutputStreamAdaptor.cpp",
@@ -143,6 +142,7 @@
         "android/graphics/NinePatch.cpp",
         "android/graphics/NinePatchPeeker.cpp",
         "android/graphics/Paint.cpp",
+        "android/graphics/PaintFilter.cpp",
         "android/graphics/Path.cpp",
         "android/graphics/PathMeasure.cpp",
         "android/graphics/PathEffect.cpp",
@@ -220,7 +220,6 @@
         "external/skia/src/image",
         "external/skia/src/images",
         "frameworks/base/media/jni",
-        "libcore/include",
         "system/media/camera/include",
         "system/media/private/camera/include",
     ],
diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp
similarity index 65%
rename from core/jni/android/graphics/DrawFilter.cpp
rename to core/jni/android/graphics/PaintFilter.cpp
index c1dc0dd..182b22b 100644
--- a/core/jni/android/graphics/DrawFilter.cpp
+++ b/core/jni/android/graphics/PaintFilter.cpp
@@ -15,36 +15,43 @@
 ** limitations under the License.
 */
 
-// This file was generated from the C++ include file: SkColorFilter.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <android_runtime/AndroidRuntime.h>
 
 #include "core_jni_helpers.h"
 
-#include "SkDrawFilter.h"
-#include "SkPaintFlagsDrawFilter.h"
+#include "hwui/PaintFilter.h"
 #include "SkPaint.h"
 
 namespace android {
 
-// Custom version of SkPaintFlagsDrawFilter that also calls setFilterQuality.
-class CompatFlagsDrawFilter : public SkPaintFlagsDrawFilter {
+class PaintFlagsFilter : public PaintFilter {
 public:
-    CompatFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags,
-            SkFilterQuality desiredQuality)
-    : SkPaintFlagsDrawFilter(clearFlags, setFlags)
+    PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) {
+        fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags);
+        fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags);
+    }
+    void filter(SkPaint* paint) override {
+        paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+    }
+
+private:
+    uint16_t fClearFlags;
+    uint16_t fSetFlags;
+};
+
+// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality.
+class CompatPaintFlagsFilter : public PaintFlagsFilter {
+public:
+    CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality)
+    : PaintFlagsFilter(clearFlags, setFlags)
     , fDesiredQuality(desiredQuality) {
     }
 
-    virtual bool filter(SkPaint* paint, Type type) {
-        SkPaintFlagsDrawFilter::filter(paint, type);
+    virtual void filter(SkPaint* paint) {
+        PaintFlagsFilter::filter(paint);
         paint->setFilterQuality(fDesiredQuality);
-        return true;
     }
 
 private:
@@ -61,16 +68,16 @@
     return result;
 }
 
-class SkDrawFilterGlue {
+class PaintFilterGlue {
 public:
 
     static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
-        SkDrawFilter* obj = reinterpret_cast<SkDrawFilter*>(objHandle);
+        PaintFilter* obj = reinterpret_cast<PaintFilter*>(objHandle);
         SkSafeUnref(obj);
     }
 
-    static jlong CreatePaintFlagsDF(JNIEnv* env, jobject clazz,
-                                    jint clearFlags, jint setFlags) {
+    static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz,
+                                        jint clearFlags, jint setFlags) {
         if (clearFlags | setFlags) {
             // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
             // longer has a Skia equivalent flag (instead it corresponds to
@@ -79,16 +86,16 @@
             const bool turnFilteringOn = hadFiltering(setFlags);
             const bool turnFilteringOff = hadFiltering(clearFlags);
 
-            SkDrawFilter* filter;
+            PaintFilter* filter;
             if (turnFilteringOn) {
                 // Turning filtering on overrides turning it off.
-                filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+                filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
                         kLow_SkFilterQuality);
             } else if (turnFilteringOff) {
-                filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+                filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
                         kNone_SkFilterQuality);
             } else {
-                filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags);
+                filter = new PaintFlagsFilter(clearFlags, setFlags);
             }
             return reinterpret_cast<jlong>(filter);
         } else {
@@ -98,11 +105,11 @@
 };
 
 static const JNINativeMethod drawfilter_methods[] = {
-    {"nativeDestructor", "(J)V", (void*) SkDrawFilterGlue::finalizer}
+    {"nativeDestructor", "(J)V", (void*) PaintFilterGlue::finalizer}
 };
 
 static const JNINativeMethod paintflags_methods[] = {
-    {"nativeConstructor","(II)J", (void*) SkDrawFilterGlue::CreatePaintFlagsDF}
+    {"nativeConstructor","(II)J", (void*) PaintFilterGlue::CreatePaintFlagsFilter}
 };
 
 int register_android_graphics_DrawFilter(JNIEnv* env) {
@@ -110,7 +117,7 @@
                                       NELEM(drawfilter_methods));
     result |= RegisterMethodsOrDie(env, "android/graphics/PaintFlagsDrawFilter", paintflags_methods,
                                    NELEM(paintflags_methods));
-    
+
     return 0;
 }
 
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 484b33f..3b024b4 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -22,13 +22,13 @@
 #include <androidfw/ResourceTypes.h>
 #include <hwui/Canvas.h>
 #include <hwui/Paint.h>
+#include <hwui/PaintFilter.h>
 #include <hwui/Typeface.h>
 #include <minikin/Layout.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedStringChars.h>
 
 #include "Bitmap.h"
-#include "SkDrawFilter.h"
 #include "SkGraphics.h"
 #include "SkRegion.h"
 #include "SkVertices.h"
@@ -582,8 +582,9 @@
     env->ReleaseStringChars(text, jchars);
 }
 
-static void setDrawFilter(jlong canvasHandle, jlong filterHandle) {
-    get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
+static void setPaintFilter(jlong canvasHandle, jlong filterHandle) {
+    PaintFilter* paintFilter = reinterpret_cast<PaintFilter*>(filterHandle);
+    get_canvas(canvasHandle)->setPaintFilter(sk_ref_sp(paintFilter));
 }
 
 static void freeCaches(JNIEnv* env, jobject) {
@@ -633,7 +634,7 @@
     {"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
     {"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
     {"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
-    {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+    {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
 };
 
 // If called from Canvas these are regular JNI
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
index 16081b4..b3cb4d2 100644
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -17,7 +17,6 @@
 #ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
 #define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
 
-#include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <media/MediaAnalyticsItem.h>
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index 41a81ac..9eb6f8d 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "MeasuredParagraph"
 
 #include "GraphicsJNI.h"
-#include "ScopedIcuLocale.h"
 #include "unicode/locid.h"
 #include "unicode/brkiter.h"
 #include "utils/misc.h"
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index ad84858..fec5b69 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -16,7 +16,6 @@
 
 #define LOG_TAG "StaticLayout"
 
-#include "ScopedIcuLocale.h"
 #include "unicode/locid.h"
 #include "unicode/brkiter.h"
 #include "utils/misc.h"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2a5c57..472df1a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2437,7 +2437,8 @@
     <permission android:name="android.permission.ASEC_RENAME"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows applications to write the apn settings.
+    <!-- @SystemApi Allows applications to write the apn settings and read sensitive fields of
+         an existing apn settings like user and password.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_APN_SETTINGS"
         android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable-hdpi/ic_grayedout_printer.png b/core/res/res/drawable-hdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/core/res/res/drawable-hdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_grayedout_printer.png b/core/res/res/drawable-mdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/core/res/res/drawable-mdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_grayedout_printer.png b/core/res/res/drawable-xhdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/core/res/res/drawable-xhdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values-land/dimens_package_installer.xml b/core/res/res/values-land/dimens_package_installer.xml
new file mode 100644
index 0000000..72f4ec2
--- /dev/null
+++ b/core/res/res/values-land/dimens_package_installer.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 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.
+  -->
+
+<!-- Landscape dimensions for the permission grant dialog. -->
+<resources>
+    <!-- 37:20 == 65% width -->
+    <dimen name="permissionGrantDialogWidth">37</dimen>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc334-mnc03/config.xml
similarity index 60%
rename from packages/PrintSpooler/res/drawable/print_warning.xml
rename to core/res/res/values-mcc334-mnc03/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc334-mnc03/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc334-mnc030/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc334-mnc030/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc334-mnc030/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc704-mnc03/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc704-mnc03/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc704-mnc03/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc706-mnc04/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc706-mnc04/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc706-mnc04/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc712-mnc04/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc712-mnc04/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc712-mnc04/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc716-mnc06/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc716-mnc06/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc716-mnc06/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc716-mnc10/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc716-mnc10/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc716-mnc10/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc716-mnc17/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc716-mnc17/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc716-mnc17/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc722-mnc07/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc722-mnc07/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc722-mnc07/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc732-mnc123/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc732-mnc123/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc732-mnc123/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc740-mnc00/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc740-mnc00/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc740-mnc00/config.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="96dp"
-        android:height="96dp"
-        android:viewportWidth="96.0"
-        android:viewportHeight="96.0">
-    <path
-        android:fillColor="#C8CCCE"
-        android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+    <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
new file mode 100644
index 0000000..688040b
--- /dev/null
+++ b/core/res/res/values-night/colors.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+
+  NOTE: You might also want to edit: packages/SystemUI/res/values-night/colors.xml
+  -->
+<resources>
+    <!-- The primary text color if the text is on top of a dark background.
+    This is also affects colorized notifications with dark backgrounds. -->
+    <color name="notification_primary_text_color_dark">#dadada</color>
+
+    <!-- The secondary text color if the text is on top of a dark background. -->
+    <color name="notification_secondary_text_color_dark">#dadada</color>
+
+    <color name="notification_default_color_dark">#dadada</color>
+
+    <!-- The background color of a notification card. -->
+    <color name="notification_material_background_color">@*android:color/material_grey_900</color>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml
index 23b0a24..4eb2ff3 100644
--- a/core/res/res/values-night/values.xml
+++ b/core/res/res/values-night/values.xml
@@ -31,4 +31,9 @@
         <!-- volume background -->
         <item name="panelColorBackground">@color/material_grey_800</item>
     </style>
+
+    <style name="TextAppearance.Material.Notification">
+        <item name="textColor">@color/notification_secondary_text_color_dark</item>
+        <item name="textSize">@dimen/notification_text_size</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/core/res/res/values-port/dimens_package_installer.xml b/core/res/res/values-port/dimens_package_installer.xml
new file mode 100644
index 0000000..67cafe7
--- /dev/null
+++ b/core/res/res/values-port/dimens_package_installer.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 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.
+  -->
+
+<!-- portrait dimensions for the permission grant dialog. -->
+<resources>
+    <!-- 380:20 == 95% width -->
+    <dimen name="permissionGrantDialogWidth">380</dimen>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1862437..e4ce2b1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3414,7 +3414,7 @@
     <string-array translatable="false" name="config_batteryPackageTypeService"/>
 
     <!-- Flag indicating whether or not to enable night mode detection. -->
-    <bool name="config_enableNightMode">false</bool>
+    <bool name="config_enableNightMode">true</bool>
 
     <!-- Flag indicating that the actions buttons for a notification should be tinted with by the
          color supplied by the Notification.Builder if present. -->
diff --git a/core/res/res/values/styles_package_installer.xml b/core/res/res/values/styles_package_installer.xml
new file mode 100644
index 0000000..8bfcc8d
--- /dev/null
+++ b/core/res/res/values/styles_package_installer.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 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.
+  -->
+
+<!-- styles for the permission grant dialog. -->
+<resources>
+    <style name="PermissionGrantDialog">
+        <item name="background">?attr/windowBackground</item>
+        <item name="elevation">?attr/windowElevation</item>
+        <item name="layout_weight">@dimen/permissionGrantDialogWidth</item>
+    </style>
+
+    <style name="PermissionGrantTitleIcon">
+        <item name="layout_width">36dp</item>
+        <item name="layout_height">36dp</item>
+        <item name="tint">?attr/colorAccent</item>
+        <item name="scaleType">fitCenter</item>
+    </style>
+
+    <style name="PermissionGrantTitleMessage"
+           parent="@style/TextAppearance.DeviceDefault">
+        <item name="paddingStart">22dp</item>
+        <item name="textSize">20sp</item>
+        <item name="textColor">?attr/textColorPrimary</item>
+    </style>
+
+    <style name="PermissionGrantIndex"
+           parent="@style/TextAppearance.DeviceDefault">
+        <item name="paddingEnd">12dp</item>
+        <item name="singleLine">true</item>
+        <item name="textColor">?attr/textColorSecondary</item>
+    </style>
+
+    <style name="PermissionGrantDescription">
+        <item name="layout_marginTop">20dp</item>
+        <item name="layout_marginStart">24dp</item>
+        <item name="layout_marginBottom">16dp</item>
+        <item name="layout_marginEnd">24dp</item>
+    </style>
+
+    <style name="PermissionGrantContent">
+        <item name="layout_marginStart">16dp</item>
+        <item name="layout_marginEnd">24dp</item>
+    </style>
+
+    <style name="PermissionGrantRadioGroup">
+        <item name="layout_marginStart">8dp</item>
+        <item name="layout_marginTop">-4dp</item>
+    </style>
+
+    <style name="PermissionGrantRadioButton"
+           parent="@style/Widget.DeviceDefault.CompoundButton.RadioButton">
+        <item name="paddingStart">16dp</item>
+        <item name="paddingTop">8dp</item>
+        <item name="paddingBottom">8dp</item>
+        <item name="textSize">16sp</item>
+    </style>
+
+    <style name="PermissionGrantDetailMessage"
+           parent="@style/TextAppearance.DeviceDefault">
+        <item name="layout_marginStart">8dp</item>
+        <item name="layout_marginBottom">4dp</item>
+        <item name="textColor">?attr/textColorPrimary</item>
+        <item name="textSize">16sp</item>
+    </style>
+
+    <style name="PermissionGrantDetailMessageSpace">
+        <item name="layout_height">8dp</item>
+    </style>
+
+    <style name="PermissionGrantCheckbox"
+           parent="@style/Widget.DeviceDefault.CompoundButton.CheckBox">
+        <item name="paddingStart">16dp</item>
+        <item name="textColor">?attr/textColorSecondary</item>
+        <item name="buttonTint">?attr/textColorSecondary</item>
+        <item name="textSize">16sp</item>
+    </style>
+
+    <style name="PermissionGrantButtonBar">
+        <item name="layout_marginStart">24dp</item>
+        <item name="layout_marginEnd">16dp</item>
+        <item name="layout_marginBottom">4dp</item>
+    </style>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 99001e3..6a58b67 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1339,7 +1339,6 @@
   <java-symbol type="drawable" name="ic_text_dot" />
   <java-symbol type="drawable" name="ic_print" />
   <java-symbol type="drawable" name="ic_print_error" />
-  <java-symbol type="drawable" name="ic_grayedout_printer" />
   <java-symbol type="drawable" name="jog_dial_arrow_long_left_green" />
   <java-symbol type="drawable" name="jog_dial_arrow_long_right_red" />
   <java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" />
diff --git a/core/res/res/values/themes_package_installer.xml b/core/res/res/values/themes_package_installer.xml
new file mode 100644
index 0000000..a6341dc
--- /dev/null
+++ b/core/res/res/values/themes_package_installer.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 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.
+  -->
+
+<!-- themes for the permission grant dialog. -->
+<resources>
+    <style name="Theme.DeviceDefault.Light.Panel.PermissionGrantApp"
+           parent="@style/Theme.DeviceDefault.Light.Panel">
+        <item name="windowIsFloating">false</item>
+        <item name="windowTranslucentStatus">true</item>
+        <item name="backgroundDimEnabled">true</item>
+        <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Light.Dialog.PermissionGrant"
+           parent="@style/Theme.DeviceDefault.Light.Dialog">
+        <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item>
+        <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item>
+        <item name="checkboxStyle">@style/PermissionGrantCheckbox</item>
+        <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item>
+    </style>
+</resources>
diff --git a/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
index 5989da7..c70fc3c 100644
--- a/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
@@ -54,7 +54,7 @@
         for (int i = 0; i < reps; i++) {
             try (FileInputStream in = new FileInputStream(mSrc);
                     FileOutputStream out = new FileOutputStream(mDest)) {
-                copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+                copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
             }
         }
     }
@@ -63,7 +63,7 @@
         for (int i = 0; i < reps; i++) {
             try (FileInputStream in = new FileInputStream(mSrc);
                     FileOutputStream out = new FileOutputStream(mDest)) {
-                copyInternalSendfile(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+                copyInternalSendfile(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
             }
         }
     }
@@ -72,7 +72,7 @@
         for (int i = 0; i < reps; i++) {
             try (MemoryPipe in = MemoryPipe.createSource(mData);
                     FileOutputStream out = new FileOutputStream(mDest)) {
-                copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+                copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
             }
         }
     }
@@ -81,7 +81,7 @@
         for (int i = 0; i < reps; i++) {
             try (MemoryPipe in = MemoryPipe.createSource(mData);
                     FileOutputStream out = new FileOutputStream(mDest)) {
-                copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+                copyInternalSplice(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
             }
         }
     }
@@ -90,7 +90,7 @@
         for (int i = 0; i < reps; i++) {
             try (FileInputStream in = new FileInputStream(mSrc);
                     MemoryPipe out = MemoryPipe.createSink(mData)) {
-                copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+                copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
             }
         }
     }
@@ -99,7 +99,7 @@
         for (int i = 0; i < reps; i++) {
             try (FileInputStream in = new FileInputStream(mSrc);
                     MemoryPipe out = MemoryPipe.createSink(mData)) {
-                copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+                copyInternalSplice(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
             }
         }
     }
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index ada0366..b906d84 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -446,7 +446,10 @@
 
         final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         mContext.registerReceiver(receiver, filter);
-        assertTrue(adapter.enable());
+        // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to
+        // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable.
+        // So no assertion applied here.
+        adapter.enable();
         boolean success = false;
         try {
             success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
@@ -489,7 +492,10 @@
 
         final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         mContext.registerReceiver(receiver, filter);
-        assertTrue(adapter.disable());
+        // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to
+        // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable.
+        // So no assertion applied here.
+        adapter.disable();
         boolean success = false;
         try {
             success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index fe58116..3d114f4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -47,6 +47,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +57,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /** Test {@link TransactionExecutor} logic. */
@@ -232,6 +234,44 @@
     }
 
     @Test
+    public void testDoNotLaunchDestroyedActivity() {
+        final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = new ArrayMap<>();
+        when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+        // Assume launch transaction is still in queue, so there is no client record.
+        when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+        // An incoming destroy transaction enters binder thread (preExecute).
+        final IBinder token = mock(IBinder.class);
+        final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */,
+                token /* activityToken */);
+        destroyTransaction.setLifecycleStateRequest(
+                DestroyActivityItem.obtain(false /* finished */, 0 /* configChanges */));
+        destroyTransaction.preExecute(mTransactionHandler);
+        // The activity should be added to to-be-destroyed container.
+        assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size());
+
+        // A previous queued launch transaction runs on main thread (execute).
+        final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */,
+                token /* activityToken */);
+        final LaunchActivityItem launchItem = spy(LaunchActivityItem.obtain(
+                null /* intent */, 0 /* ident */, null /* info */, null /* curConfig */,
+                null, /* overrideConfig */ null /* compatInfo */, null /* referrer */ ,
+                null /* voiceInteractor */, 0 /* procState */, null /* state */,
+                null /* persistentState */, null /* pendingResults */,
+                null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */));
+        launchTransaction.addCallback(launchItem);
+        mExecutor.execute(launchTransaction);
+
+        // The launch transaction should not be executed because its token is in the
+        // to-be-destroyed container.
+        verify(launchItem, times(0)).execute(any(), any(), any());
+
+        // After the destroy transaction has been executed, the token should be removed.
+        mExecutor.execute(destroyTransaction);
+        assertEquals(0, mTransactionHandler.getActivitiesToBeDestroyed().size());
+    }
+
+    @Test
     public void testActivityResultRequiredStateResolution() {
         PostExecItem postExecItem = new PostExecItem(ON_RESUME);
 
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 0bc3a2d..9c9f11b 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -193,7 +193,7 @@
 
             try (MemoryPipe in = MemoryPipe.createSource(source);
                     FileOutputStream out = new FileOutputStream(dest)) {
-                FileUtils.copy(in.getFD(), out.getFD(), null, null, size);
+                FileUtils.copy(in.getFD(), out.getFD(), size, null, null, null);
             }
 
             actual = readFile(dest);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index f91d149..60e512c 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -66,7 +66,6 @@
                     Settings.System.LOCKSCREEN_DISABLED, // ?
                     Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
                     Settings.System.MUTE_STREAMS_AFFECTED, //  candidate for backup?
-                    Settings.System.NOTIFICATION_LIGHT_PULSE, // candidate for backup?
                     Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
                     Settings.System.POINTER_LOCATION, // backup candidate?
                     Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
@@ -560,10 +559,8 @@
                  Settings.Secure.LAST_SETUP_SHOWN,
                  Settings.Secure.LOCATION_CHANGER,
                  Settings.Secure.LOCATION_MODE,
-                 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate?
                  Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
                  Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, // Candidate?
                  Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
                  Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
                  Settings.Secure.MULTI_PRESS_TIMEOUT,
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 870d6b2..72290bf 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -785,4 +785,11 @@
         assertEquals(2, TextUtils.length("  "));
         assertEquals(6, TextUtils.length("Hello!"));
     }
+
+    @Test
+    public void testTrimToLengthWithEllipsis() {
+        assertEquals("ABC...", TextUtils.trimToLengthWithEllipsis("ABCDEF", 3));
+        assertEquals("ABC", TextUtils.trimToLengthWithEllipsis("ABC", 3));
+        assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 914fb74..d46c154 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -226,6 +226,26 @@
     }
 
     @Test
+    public void testTransactionCodeResolved() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Binder binder = new Binder() {
+            @Override
+            public String getTransactionName(int code) {
+              return "resolved";
+            }
+        };
+        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.time += 10;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        List<BinderCallsStats.CallStat> callStatsList =
+                bcs.getUidEntries().get(TEST_UID).getCallStatsList();
+        assertEquals(1, callStatsList.get(0).msg);
+        assertEquals("resolved", callStatsList.get(0).methodName);
+    }
+
+    @Test
     public void testParcelSize() {
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setDetailedTracking(true);
@@ -323,6 +343,42 @@
         bcs.dump(pw, new HashMap<>(), true);
     }
 
+    @Test
+    public void testGetExportedStatsWhenDetailedTrackingDisabled() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(false);
+        Binder binder = new Binder();
+        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        assertEquals(0, bcs.getExportedCallStats().size());
+    }
+
+    @Test
+    public void testGetExportedStatsWhenDetailedTrackingEnabled() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setDetailedTracking(true);
+        Binder binder = new Binder();
+        BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+        bcs.time += 10;
+        bcs.elapsedTime += 20;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+        assertEquals(1, bcs.getExportedCallStats().size());
+        BinderCallsStats.ExportedCallStat stat = bcs.getExportedCallStats().get(0);
+        assertEquals(TEST_UID, stat.uid);
+        assertEquals("android.os.Binder", stat.className);
+        assertEquals("1", stat.methodName);
+        assertEquals(10, stat.cpuTimeMicros);
+        assertEquals(10, stat.maxCpuTimeMicros);
+        assertEquals(20, stat.latencyMicros);
+        assertEquals(20, stat.maxLatencyMicros);
+        assertEquals(1, stat.callCount);
+        assertEquals(REQUEST_SIZE, stat.maxRequestSizeBytes);
+        assertEquals(REPLY_SIZE, stat.maxReplySizeBytes);
+        assertEquals(0, stat.exceptionCount);
+    }
+
     static class TestBinderCallsStats extends BinderCallsStats {
         int callingUid = TEST_UID;
         long time = 1234;
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index bf8067c..bb8add1 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -234,3 +234,8 @@
     $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
     $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:system/media/audio/ui/ChargingStarted.ogg \
     $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:system/media/audio/ui/InCallNotification.ogg \
+    $(LOCAL_PATH)/effects/ogg/NFCFailure.ogg:system/media/audio/ui/NFCFailure.ogg \
+    $(LOCAL_PATH)/effects/ogg/NFCInitiated.ogg:system/media/audio/ui/NFCInitiated.ogg \
+    $(LOCAL_PATH)/effects/ogg/NFCSuccess.ogg:system/media/audio/ui/NFCSuccess.ogg \
+    $(LOCAL_PATH)/effects/ogg/NFCTransferComplete.ogg:system/media/audio/ui/NFCTransferComplete.ogg \
+    $(LOCAL_PATH)/effects/ogg/NFCTransferInitiated.ogg:system/media/audio/ui/NFCTransferInitiated.ogg \
diff --git a/data/sounds/AudioPackage14.mk b/data/sounds/AudioPackage14.mk
new file mode 100644
index 0000000..c903a2b
--- /dev/null
+++ b/data/sounds/AudioPackage14.mk
@@ -0,0 +1,32 @@
+#
+# Audio Package 14 - P
+#
+# Include this file in a product makefile to include these audio files
+#
+#
+
+LOCAL_PATH := frameworks/base/data/sounds
+
+# Simple files that do not require renaming
+ALARM_FILES := Argon Carbon Helium Krypton Neon Oxygen Osmium Platinum Timer
+NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Titan Tethys
+RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna Titania Triton \
+	Umbriel
+EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
+	camera_focus Dock Undock Lock Unlock Trusted ChargingStarted InCallNotification \
+	NFCFailure NFCInitiated NFCSuccess NFCTransferComplete NFCTransferInitiated
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
+
+PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
+	$(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
+	$(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:system/media/audio/notifications/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
+	$(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:system/media/audio/ringtones/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
+	$(LOCAL_PATH)/effects/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
+	$(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
diff --git a/data/sounds/README.txt b/data/sounds/README.txt
index 193fd71..db20319 100644
--- a/data/sounds/README.txt
+++ b/data/sounds/README.txt
@@ -31,3 +31,13 @@
 ./effects/ogg/VideoStop_48k.ogg
   unused
 
+NFC
+---
+
+./effects/ogg/NFCFailure.ogg
+./effects/ogg/NFCInitiated.ogg
+./effects/ogg/NFCSuccess.ogg
+./effects/ogg/NFCTransferComplete.ogg
+./effects/ogg/NFCTransferInitiated.ogg
+
+referenced in AudioPackage14.mk (= AudioPackage13.mk + NFC sounds).
diff --git a/data/sounds/effects/ogg/NFCFailure.ogg b/data/sounds/effects/ogg/NFCFailure.ogg
new file mode 100644
index 0000000..e9ee662
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCFailure.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCInitiated.ogg b/data/sounds/effects/ogg/NFCInitiated.ogg
new file mode 100644
index 0000000..a86319f
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCInitiated.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCSuccess.ogg b/data/sounds/effects/ogg/NFCSuccess.ogg
new file mode 100644
index 0000000..39dfd1f
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCSuccess.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCTransferComplete.ogg b/data/sounds/effects/ogg/NFCTransferComplete.ogg
new file mode 100644
index 0000000..f00cd98
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCTransferComplete.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCTransferInitiated.ogg b/data/sounds/effects/ogg/NFCTransferInitiated.ogg
new file mode 100644
index 0000000..7be1bcb
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCTransferInitiated.ogg
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 111094b..59760ab 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -48,6 +48,11 @@
         device_uses_hwc2: {
             cflags: ["-DUSE_HWC2"],
         },
+        eng: {
+            lto: {
+                never: true,
+            },
+        },
     },
 }
 
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index e495744..ff9cf45 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -44,8 +44,8 @@
 }
 
 bool LayerProperties::setColorFilter(SkColorFilter* filter) {
-    if (mColorFilter == filter) return false;
-    SkRefCnt_SafeAssign(mColorFilter, filter);
+    if (mColorFilter.get() == filter) return false;
+    mColorFilter = sk_ref_sp(filter);
     return true;
 }
 
@@ -62,7 +62,7 @@
     setOpaque(other.opaque());
     setAlpha(other.alpha());
     setXferMode(other.xferMode());
-    setColorFilter(other.colorFilter());
+    setColorFilter(other.getColorFilter());
     return *this;
 }
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index c024373..0766e3b 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -89,9 +89,7 @@
 
     SkBlendMode xferMode() const { return mMode; }
 
-    bool setColorFilter(SkColorFilter* filter);
-
-    SkColorFilter* colorFilter() const { return mColorFilter; }
+    SkColorFilter* getColorFilter() const { return mColorFilter.get(); }
 
     // Sets alpha, xfermode, and colorfilter from an SkPaint
     // paint may be NULL, in which case defaults will be set
@@ -105,6 +103,7 @@
     LayerProperties();
     ~LayerProperties();
     void reset();
+    bool setColorFilter(SkColorFilter* filter);
 
     // Private since external users should go through properties().effectiveLayerType()
     LayerType type() const { return mType; }
@@ -116,7 +115,7 @@
     bool mOpaque;
     uint8_t mAlpha;
     SkBlendMode mMode;
-    SkColorFilter* mColorFilter = nullptr;
+    sk_sp<SkColorFilter> mColorFilter;
 };
 
 /*
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 7b41f89..17f1a3b 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -21,6 +21,7 @@
 #include "VectorDrawable.h"
 #include "hwui/Bitmap.h"
 #include "hwui/MinikinUtils.h"
+#include "hwui/PaintFilter.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 
 #include <SkAnimatedImage.h>
@@ -28,7 +29,6 @@
 #include <SkColorFilter.h>
 #include <SkColorSpaceXformCanvas.h>
 #include <SkDeque.h>
-#include <SkDrawFilter.h>
 #include <SkDrawable.h>
 #include <SkGraphics.h>
 #include <SkImage.h>
@@ -40,6 +40,8 @@
 #include <SkTextBlob.h>
 
 #include <memory>
+#include <optional>
+#include <utility>
 
 namespace android {
 
@@ -211,7 +213,7 @@
     Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m)
             : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
     Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
-            : mType(Type::Path), mOp(op), mMatrix(m), mPath(&path) {}
+            : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
 
     void apply(SkCanvas* canvas) const {
         canvas->setMatrix(mMatrix);
@@ -223,7 +225,7 @@
                 canvas->clipRRect(mRRect, mOp);
                 break;
             case Type::Path:
-                canvas->clipPath(*mPath.get(), mOp);
+                canvas->clipPath(mPath.value(), mOp);
                 break;
         }
     }
@@ -240,7 +242,7 @@
     SkMatrix mMatrix;
 
     // These are logically a union (tracked separately due to non-POD path).
-    SkTLazy<SkPath> mPath;
+    std::optional<SkPath> mPath;
     SkRRect mRRect;
 };
 
@@ -400,12 +402,12 @@
 // Canvas state operations: Filters
 // ----------------------------------------------------------------------------
 
-SkDrawFilter* SkiaCanvas::getDrawFilter() {
-    return mCanvas->getDrawFilter();
+PaintFilter* SkiaCanvas::getPaintFilter() {
+    return mPaintFilter.get();
 }
 
-void SkiaCanvas::setDrawFilter(SkDrawFilter* drawFilter) {
-    mCanvas->setDrawFilter(drawFilter);
+void SkiaCanvas::setPaintFilter(sk_sp<PaintFilter> paintFilter) {
+    mPaintFilter = std::move(paintFilter);
 }
 
 // ----------------------------------------------------------------------------
@@ -439,8 +441,15 @@
     mCanvas->drawColor(color, mode);
 }
 
+SkiaCanvas::PaintCoW&& SkiaCanvas::filterPaint(PaintCoW&& paint) const {
+    if (mPaintFilter) {
+        mPaintFilter->filter(&paint.writeable());
+    }
+    return std::move(paint);
+}
+
 void SkiaCanvas::drawPaint(const SkPaint& paint) {
-    mCanvas->drawPaint(paint);
+    mCanvas->drawPaint(*filterPaint(paint));
 }
 
 // ----------------------------------------------------------------------------
@@ -457,53 +466,53 @@
         pts[i].set(points[0], points[1]);
         points += 2;
     }
-    mCanvas->drawPoints(mode, count, pts.get(), paint);
+    mCanvas->drawPoints(mode, count, pts.get(), *filterPaint(paint));
 }
 
 void SkiaCanvas::drawPoint(float x, float y, const SkPaint& paint) {
-    mCanvas->drawPoint(x, y, paint);
+    mCanvas->drawPoint(x, y, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
-    this->drawPoints(points, count, paint, SkCanvas::kPoints_PointMode);
+    this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kPoints_PointMode);
 }
 
 void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
                           const SkPaint& paint) {
-    mCanvas->drawLine(startX, startY, stopX, stopY, paint);
+    mCanvas->drawLine(startX, startY, stopX, stopY, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
     if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return;
-    this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
+    this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kLines_PointMode);
 }
 
 void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
-    mCanvas->drawRect({left, top, right, bottom}, paint);
+    mCanvas->drawRect({left, top, right, bottom}, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
-    mCanvas->drawRegion(region, paint);
+    mCanvas->drawRegion(region, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
                                const SkPaint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
-    mCanvas->drawRoundRect(rect, rx, ry, paint);
+    mCanvas->drawRoundRect(rect, rx, ry, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
     if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
-    mCanvas->drawCircle(x, y, radius, paint);
+    mCanvas->drawCircle(x, y, radius, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
-    mCanvas->drawOval(oval, paint);
+    mCanvas->drawOval(oval, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -511,9 +520,9 @@
     if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
     if (fabs(sweepAngle) >= 360.0f) {
-        mCanvas->drawOval(arc, paint);
+        mCanvas->drawOval(arc, *filterPaint(paint));
     } else {
-        mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+        mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, *filterPaint(paint));
     }
 }
 
@@ -522,19 +531,19 @@
     if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
         return;
     }
-    mCanvas->drawPath(path, paint);
+    mCanvas->drawPath(path, *filterPaint(paint));
 }
 
 void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
-    mCanvas->drawVertices(vertices, mode, paint);
+    mCanvas->drawVertices(vertices, mode, *filterPaint(paint));
 }
 
 // ----------------------------------------------------------------------------
 // Canvas draw operations: Bitmaps
 // ----------------------------------------------------------------------------
 
-const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
-                                     sk_sp<SkColorFilter> colorSpaceFilter) {
+SkiaCanvas::PaintCoW&& SkiaCanvas::filterBitmap(PaintCoW&& paint,
+                                                sk_sp<SkColorFilter> colorSpaceFilter) const {
     /* We don't apply the colorSpace filter if this canvas is already wrapped with
      * a SkColorSpaceXformCanvas since it already takes care of converting the
      * contents of the bitmap into the appropriate colorspace.  The mCanvasWrapper
@@ -542,39 +551,31 @@
      * to have a non-sRGB colorspace.
      */
     if (!mCanvasWrapper && colorSpaceFilter) {
-        if (origPaint) {
-            *tmpPaint = *origPaint;
-        }
-
-        if (tmpPaint->getColorFilter()) {
-            tmpPaint->setColorFilter(
-                    SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter));
-            LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
+        SkPaint& tmpPaint = paint.writeable();
+        if (tmpPaint.getColorFilter()) {
+            tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(tmpPaint.refColorFilter(),
+                                                                     std::move(colorSpaceFilter)));
+            LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter());
         } else {
-            tmpPaint->setColorFilter(colorSpaceFilter);
+            tmpPaint.setColorFilter(std::move(colorSpaceFilter));
         }
-
-        return tmpPaint;
-    } else {
-        return origPaint;
     }
+    return filterPaint(std::move(paint));
 }
 
 void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mCanvas->drawImage(image, left, top, addFilter(paint, &tmpPaint, colorFilter));
+    mCanvas->drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)));
 }
 
 void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) {
     SkAutoCanvasRestore acr(mCanvas, true);
     mCanvas->concat(matrix);
 
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mCanvas->drawImage(image, 0, 0, addFilter(paint, &tmpPaint, colorFilter));
+    mCanvas->drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)));
 }
 
 void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight,
@@ -583,10 +584,9 @@
     SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
 
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mCanvas->drawImageRect(image, srcRect, dstRect, addFilter(paint, &tmpPaint, colorFilter),
+    mCanvas->drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)),
                            SkCanvas::kFast_SrcRectConstraint);
 }
 
@@ -665,21 +665,20 @@
 #endif
 
     // cons-up a shader for the bitmap
-    SkPaint tmpPaint;
-    if (paint) {
-        tmpPaint = *paint;
-    }
+    PaintCoW paintCoW(paint);
+    SkPaint& tmpPaint = paintCoW.writeable();
 
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
     sk_sp<SkShader> shader =
             image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
     if (colorFilter) {
-        shader = shader->makeWithColorFilter(colorFilter);
+        shader = shader->makeWithColorFilter(std::move(colorFilter));
     }
-    tmpPaint.setShader(shader);
+    tmpPaint.setShader(std::move(shader));
 
-    mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint);
+    mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate,
+                          *filterPaint(std::move(paintCoW)));
 }
 
 void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, float dstLeft,
@@ -706,10 +705,10 @@
     lattice.fBounds = nullptr;
     SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
 
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
+    mCanvas->drawImageLattice(image.get(), lattice, dst,
+                              filterBitmap(paint, std::move(colorFilter)));
 }
 
 double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
@@ -732,6 +731,9 @@
     // glyphs centered or right-aligned; the offset above takes
     // care of all alignment.
     SkPaint paintCopy(paint);
+    if (mPaintFilter) {
+        mPaintFilter->filter(&paintCopy);
+    }
     paintCopy.setTextAlign(SkPaint::kLeft_Align);
     SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
     // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
@@ -760,6 +762,9 @@
     // glyphs centered or right-aligned; the offsets take care of
     // that portion of the alignment.
     SkPaint paintCopy(paint);
+    if (mPaintFilter) {
+        mPaintFilter->filter(&paintCopy);
+    }
     paintCopy.setTextAlign(SkPaint::kLeft_Align);
     SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
 
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 3efc22a..24b7ec6 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -22,7 +22,9 @@
 #include "hwui/Canvas.h"
 
 #include <SkCanvas.h>
-#include <SkTLazy.h>
+
+#include <cassert>
+#include <optional>
 
 namespace android {
 
@@ -87,8 +89,8 @@
     virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override;
     virtual bool clipPath(const SkPath* path, SkClipOp op) override;
 
-    virtual SkDrawFilter* getDrawFilter() override;
-    virtual void setDrawFilter(SkDrawFilter* drawFilter) override;
+    virtual PaintFilter* getPaintFilter() override;
+    virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) override;
 
     virtual SkCanvasState* captureCanvasState() const override;
 
@@ -158,6 +160,46 @@
                                   const SkPaint& paint, const SkPath& path, size_t start,
                                   size_t end) override;
 
+    /** This class acts as a copy on write SkPaint.
+     *
+     *  Initially this will be the SkPaint passed to the contructor.
+     *  The first time writable() is called this will become a copy of the
+     *  initial SkPaint (or a default SkPaint if nullptr).
+     */
+    struct PaintCoW {
+        PaintCoW(const SkPaint& that) : mPtr(&that) {}
+        PaintCoW(const SkPaint* ptr) : mPtr(ptr) {}
+        PaintCoW(const PaintCoW&) = delete;
+        PaintCoW(PaintCoW&&) = delete;
+        PaintCoW& operator=(const PaintCoW&) = delete;
+        PaintCoW& operator=(PaintCoW&&) = delete;
+        SkPaint& writeable() {
+            if (!mStorage) {
+                if (!mPtr) {
+                    mStorage.emplace();
+                } else {
+                    mStorage.emplace(*mPtr);
+                }
+                mPtr = &*mStorage;
+            }
+            return *mStorage;
+        }
+        operator const SkPaint*() const { return mPtr; }
+        const SkPaint* operator->() const { assert(mPtr); return mPtr; }
+        const SkPaint& operator*() const { assert(mPtr); return *mPtr; }
+        explicit operator bool() { return mPtr != nullptr; }
+    private:
+        const SkPaint* mPtr;
+        std::optional<SkPaint> mStorage;
+    };
+
+    /** Filters the paint using the current paint filter.
+     *
+     *  @param paint the paint to filter. Will be initialized with the default
+     *      SkPaint before filtering if filtering is required.
+     */
+    PaintCoW&& filterPaint(PaintCoW&& paint) const;
+
 private:
     struct SaveRec {
         int saveCount;
@@ -174,8 +216,15 @@
 
     void drawPoints(const float* points, int count, const SkPaint& paint, SkCanvas::PointMode mode);
 
-    const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
-                             sk_sp<SkColorFilter> colorSpaceFilter);
+    /** Filters the paint for bitmap drawing.
+     *
+     *  After filtering the paint for bitmap drawing,
+     *  also calls filterPaint on the paint.
+     *
+     *  @param paint the paint to filter. Will be initialized with the default
+     *      SkPaint before filtering if filtering is required.
+     */
+    PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter) const;
 
     class Clip;
 
@@ -185,6 +234,7 @@
                                                // unless it is the same as mCanvasOwned.get()
     std::unique_ptr<SkDeque> mSaveStack;       // lazily allocated, tracks partial saves.
     std::vector<Clip> mClipStack;              // tracks persistent clips.
+    sk_sp<PaintFilter> mPaintFilter;
 };
 
 }  // namespace android
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index e84b9ac..af0ae05 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -59,12 +59,6 @@
         onPropertyChanged();                                          \
         retVal;                                                       \
     })
-#define UPDATE_SKPROP(field, value)                                    \
-    ({                                                                 \
-        bool retVal = ((field) != (value));                            \
-        if ((field) != (value)) SkRefCnt_SafeAssign((field), (value)); \
-        retVal;                                                        \
-    })
 
 /* A VectorDrawable is composed of a tree of nodes.
  * Each node can be a group node, or a path.
@@ -223,29 +217,28 @@
             int fillType = 0; /* non-zero or kWinding_FillType in Skia */
         };
         explicit FullPathProperties(Node* mNode) : Properties(mNode), mTrimDirty(false) {}
-        ~FullPathProperties() {
-            SkSafeUnref(fillGradient);
-            SkSafeUnref(strokeGradient);
-        }
+        ~FullPathProperties() {}
         void syncProperties(const FullPathProperties& prop) {
             mPrimitiveFields = prop.mPrimitiveFields;
             mTrimDirty = true;
-            UPDATE_SKPROP(fillGradient, prop.fillGradient);
-            UPDATE_SKPROP(strokeGradient, prop.strokeGradient);
+            fillGradient = prop.fillGradient;
+            strokeGradient = prop.strokeGradient;
             onPropertyChanged();
         }
         void setFillGradient(SkShader* gradient) {
-            if (UPDATE_SKPROP(fillGradient, gradient)) {
+            if (fillGradient.get() != gradient) {
+                fillGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
         void setStrokeGradient(SkShader* gradient) {
-            if (UPDATE_SKPROP(strokeGradient, gradient)) {
+            if (strokeGradient.get() != gradient) {
+                strokeGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        SkShader* getFillGradient() const { return fillGradient; }
-        SkShader* getStrokeGradient() const { return strokeGradient; }
+        SkShader* getFillGradient() const { return fillGradient.get(); }
+        SkShader* getStrokeGradient() const { return strokeGradient.get(); }
         float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
         void setStrokeWidth(float strokeWidth) {
             VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -325,8 +318,8 @@
             count,
         };
         PrimitiveFields mPrimitiveFields;
-        SkShader* fillGradient = nullptr;
-        SkShader* strokeGradient = nullptr;
+        sk_sp<SkShader> fillGradient;
+        sk_sp<SkShader> strokeGradient;
     };
 
     // Called from UI thread
@@ -550,8 +543,7 @@
             SkRect bounds;
             int scaledWidth = 0;
             int scaledHeight = 0;
-            SkColorFilter* colorFilter = nullptr;
-            ~NonAnimatableProperties() { SkSafeUnref(colorFilter); }
+            sk_sp<SkColorFilter> colorFilter;
         } mNonAnimatableProperties;
         bool mNonAnimatablePropertiesDirty = true;
 
@@ -561,8 +553,7 @@
         void syncNonAnimatableProperties(const TreeProperties& prop) {
             // Copy over the data that can only be changed in UI thread
             if (mNonAnimatableProperties.colorFilter != prop.mNonAnimatableProperties.colorFilter) {
-                SkRefCnt_SafeAssign(mNonAnimatableProperties.colorFilter,
-                                    prop.mNonAnimatableProperties.colorFilter);
+                mNonAnimatableProperties.colorFilter = prop.mNonAnimatableProperties.colorFilter;
             }
             mNonAnimatableProperties = prop.mNonAnimatableProperties;
         }
@@ -599,12 +590,13 @@
             }
         }
         void setColorFilter(SkColorFilter* filter) {
-            if (UPDATE_SKPROP(mNonAnimatableProperties.colorFilter, filter)) {
+            if (mNonAnimatableProperties.colorFilter.get() != filter) {
+                mNonAnimatableProperties.colorFilter = sk_ref_sp(filter);
                 mNonAnimatablePropertiesDirty = true;
                 mTree->onPropertyChanged(this);
             }
         }
-        SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter; }
+        SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter.get(); }
 
         float getViewportWidth() const { return mNonAnimatableProperties.viewportWidth; }
         float getViewportHeight() const { return mNonAnimatableProperties.viewportHeight; }
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index fb6bd6f..af7f013 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -23,7 +23,7 @@
 #include "Typeface.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 
-#include <SkDrawFilter.h>
+#include "hwui/PaintFilter.h"
 
 namespace android {
 
@@ -40,10 +40,10 @@
 
 void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
     uint32_t flags;
-    SkDrawFilter* drawFilter = getDrawFilter();
-    if (drawFilter) {
+    PaintFilter* paintFilter = getPaintFilter();
+    if (paintFilter) {
         SkPaint paintCopy(paint);
-        drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+        paintFilter->filter(&paintCopy);
         flags = paintCopy.getFlags();
     } else {
         flags = paint.getFlags();
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 88b12b2..5d380a6 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -39,6 +39,7 @@
 }
 
 namespace android {
+class PaintFilter;
 
 namespace uirenderer {
 class CanvasPropertyPaint;
@@ -213,8 +214,8 @@
     virtual bool clipPath(const SkPath* path, SkClipOp op) = 0;
 
     // filters
-    virtual SkDrawFilter* getDrawFilter() = 0;
-    virtual void setDrawFilter(SkDrawFilter* drawFilter) = 0;
+    virtual PaintFilter* getPaintFilter() = 0;
+    virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) = 0;
 
     // WebView only
     virtual SkCanvasState* captureCanvasState() const { return nullptr; }
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
new file mode 100644
index 0000000..bf5627e
--- /dev/null
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -0,0 +1,19 @@
+#ifndef ANDROID_GRAPHICS_PAINT_FILTER_H_
+#define ANDROID_GRAPHICS_PAINT_FILTER_H_
+
+class SkPaint;
+
+namespace android {
+
+class PaintFilter : public SkRefCnt {
+public:
+    /**
+     *  Called with the paint that will be used to draw.
+     *  The implementation may modify the paint as they wish.
+     */
+    virtual void filter(SkPaint*) = 0;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index c195a8e..85fdc103 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -156,10 +156,10 @@
                             SkPaint* paint) {
     paint->setFilterQuality(kLow_SkFilterQuality);
     if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
-        properties.xferMode() != SkBlendMode::kSrcOver || properties.colorFilter() != nullptr) {
+        properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) {
         paint->setAlpha(properties.alpha() * alphaMultiplier);
         paint->setBlendMode(properties.xferMode());
-        paint->setColorFilter(sk_ref_sp(properties.colorFilter()));
+        paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
         return true;
     }
     return false;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6fb2ee0..46e39aa 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -46,9 +46,11 @@
 
     mDisplayList->attachRecorder(&mRecorder, SkIRect::MakeWH(width, height));
     SkCanvas* canvas = &mRecorder;
-    mWrappedCanvas = makeTransformCanvas(&mRecorder, renderNode->usageHint());
-    if (mWrappedCanvas) {
-        canvas = mWrappedCanvas.get();
+    if (renderNode) {
+        mWrappedCanvas = makeTransformCanvas(&mRecorder, renderNode->usageHint());
+        if (mWrappedCanvas) {
+            canvas = mWrappedCanvas.get();
+        }
     }
     SkiaCanvas::reset(canvas);
 }
@@ -155,12 +157,45 @@
 // Recording Canvas draw operations: Bitmaps
 // ----------------------------------------------------------------------------
 
+SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint,
+                                                         sk_sp<SkColorFilter> colorSpaceFilter) {
+    bool fixBlending = false;
+    bool fixAA = false;
+    if (paint) {
+        // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
+        // older.
+        fixBlending = sApiLevel <= 27 && paint->getBlendMode() == SkBlendMode::kClear;
+        fixAA = paint->isAntiAlias();
+    }
+
+    if (fixBlending || fixAA || colorSpaceFilter) {
+        SkPaint& tmpPaint = paint.writeable();
+
+        if (fixBlending) {
+            tmpPaint.setBlendMode(SkBlendMode::kDstOut);
+        }
+
+        if (colorSpaceFilter) {
+            if (tmpPaint.getColorFilter()) {
+                tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(
+                       tmpPaint.refColorFilter(), std::move(colorSpaceFilter)));
+            } else {
+                tmpPaint.setColorFilter(std::move(colorSpaceFilter));
+            }
+            LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter());
+        }
+
+        // disabling AA on bitmap draws matches legacy HWUI behavior
+        tmpPaint.setAntiAlias(false);
+    }
+
+    return filterPaint(std::move(paint));
+}
 
 void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mRecorder.drawImage(image, left, top, bitmapPaint(paint, &tmpPaint, colorFilter));
+    mRecorder.drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)));
     // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means
     // it is not safe to store a raw SkImage pointer, because the image object will be destroyed
     // when this function ends.
@@ -173,10 +208,9 @@
     SkAutoCanvasRestore acr(&mRecorder, true);
     concat(matrix);
 
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mRecorder.drawImage(image, 0, 0, bitmapPaint(paint, &tmpPaint, colorFilter));
+    mRecorder.drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)));
     if (!bitmap.isImmutable() && image.get() && !image->unique()) {
         mDisplayList->mMutableImages.push_back(image.get());
     }
@@ -188,10 +222,9 @@
     SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
 
-    SkPaint tmpPaint;
     sk_sp<SkColorFilter> colorFilter;
     sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    mRecorder.drawImageRect(image, srcRect, dstRect, bitmapPaint(paint, &tmpPaint, colorFilter),
+    mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)),
                             SkCanvas::kFast_SrcRectConstraint);
     if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() &&
         !dstRect.isEmpty()) {
@@ -223,23 +256,16 @@
     lattice.fBounds = nullptr;
     SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
 
-    SkPaint tmpPaint;
-    sk_sp<SkColorFilter> colorFilter;
-    sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
-    const SkPaint* filteredPaint = bitmapPaint(paint, &tmpPaint, colorFilter);
+    PaintCoW filteredPaint(paint);
     // Besides kNone, the other three SkFilterQualities are treated the same. And Android's
     // Java API only supports kLow and kNone anyway.
     if (!filteredPaint || filteredPaint->getFilterQuality() == kNone_SkFilterQuality) {
-        if (filteredPaint != &tmpPaint) {
-            if (paint) {
-                tmpPaint = *paint;
-            }
-            filteredPaint = &tmpPaint;
-        }
-        tmpPaint.setFilterQuality(kLow_SkFilterQuality);
+        filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality);
     }
-
-    mRecorder.drawImageLattice(image.get(), lattice, dst, filteredPaint);
+    sk_sp<SkColorFilter> colorFilter;
+    sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
+    mRecorder.drawImageLattice(image.get(), lattice, dst,
+                               filterBitmap(std::move(filteredPaint), std::move(colorFilter)));
     if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) {
         mDisplayList->mMutableImages.push_back(image.get());
     }
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index b69acbf..50d84d6 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -90,44 +90,7 @@
      */
     void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height);
 
-    inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint,
-                                             sk_sp<SkColorFilter> colorSpaceFilter) {
-        bool fixBlending = false;
-        bool fixAA = false;
-        if (origPaint) {
-            // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
-            // older.
-            fixBlending = sApiLevel <= 27 && origPaint->getBlendMode() == SkBlendMode::kClear;
-            fixAA = origPaint->isAntiAlias();
-        }
-
-        if (fixBlending || fixAA || colorSpaceFilter) {
-            if (origPaint) {
-                *tmpPaint = *origPaint;
-            }
-
-            if (fixBlending) {
-                tmpPaint->setBlendMode(SkBlendMode::kDstOut);
-            }
-
-            if (colorSpaceFilter) {
-                if (tmpPaint->getColorFilter()) {
-                    tmpPaint->setColorFilter(SkColorFilter::MakeComposeFilter(
-                            tmpPaint->refColorFilter(), colorSpaceFilter));
-                } else {
-                    tmpPaint->setColorFilter(colorSpaceFilter);
-                }
-                LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
-            }
-
-            // disabling AA on bitmap draws matches legacy HWUI behavior
-            tmpPaint->setAntiAlias(false);
-            return tmpPaint;
-        } else {
-            return origPaint;
-        }
-    }
-
+    PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter);
 };
 
 };  // namespace skiapipeline
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index b747291..61c412d 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -2137,11 +2137,17 @@
      */
     public static final int CALL_STATUS_ERROR_IO = 4;
 
+    /** Status code represents that the call has been skipped. For example, a {@link #seekTo}
+     * request may be skipped if it is followed by another {@link #seekTo} request.
+     * @see EventCallback#onCallCompleted
+     */
+    public static final int CALL_STATUS_SKIPPED = 5;
+
     /** Status code represents that DRM operation is called before preparing a DRM scheme through
      *  {@link #prepareDrm}.
      * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
-    public static final int CALL_STATUS_NO_DRM_SCHEME = 5;
+    public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
 
     /**
      * @hide
@@ -2153,6 +2159,7 @@
             CALL_STATUS_BAD_VALUE,
             CALL_STATUS_PERMISSION_DENIED,
             CALL_STATUS_ERROR_IO,
+            CALL_STATUS_SKIPPED,
             CALL_STATUS_NO_DRM_SCHEME})
     @Retention(RetentionPolicy.SOURCE)
     public @interface CallStatus {}
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 5a71afd..dccfd7a 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -203,8 +203,6 @@
      * playback will continue from where it was paused. If playback had
      * been stopped, or never started before, playback will start at the
      * beginning.
-     *
-     * @throws IllegalStateException if it is called in an invalid state
      */
     @Override
     public void play() {
@@ -226,8 +224,6 @@
      * call prepare(). For streams, you should call prepare(),
      * which returns immediately, rather than blocking until enough data has been
      * buffered.
-     *
-     * @throws IllegalStateException if it is called in an invalid state
      */
     @Override
     public void prepare() {
@@ -243,9 +239,6 @@
 
     /**
      * Pauses playback. Call play() to resume.
-     *
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized.
      */
     @Override
     public void pause() {
@@ -351,23 +344,22 @@
      * Sets the data source as described by a DataSourceDesc.
      *
      * @param dsd the descriptor of data source you want to play
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws NullPointerException if dsd is null
      */
     @Override
     public void setDataSource(@NonNull DataSourceDesc dsd) {
         addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
             @Override
-            void process() {
-                Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
-                // TODO: setDataSource could update exist data source
+            void process() throws IOException {
+                Preconditions.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+                int state = getState();
+                if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
+                    throw new IllegalStateException("called in wrong state " + state);
+                }
+
                 synchronized (mSrcLock) {
                     mCurrentDSD = dsd;
                     mCurrentSrcId = mSrcIdGenerator++;
-                    try {
-                        handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
-                    } catch (IOException e) {
-                    }
+                    handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
                 }
             }
         });
@@ -386,7 +378,7 @@
         addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
             @Override
             void process() {
-                Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+                Preconditions.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
                 synchronized (mSrcLock) {
                     mNextDSDs = new ArrayList<DataSourceDesc>(1);
                     mNextDSDs.add(dsd);
@@ -1282,7 +1274,7 @@
         addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
             @Override
             void process() {
-                Preconditions.checkNotNull(params, "the BufferingParams cannot be null");
+                Preconditions.checkArgument(params != null, "the BufferingParams cannot be null");
                 _setBufferingParams(params);
             }
         });
@@ -1346,7 +1338,7 @@
         addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
             @Override
             void process() {
-                Preconditions.checkNotNull(params, "the PlaybackParams cannot be null");
+                Preconditions.checkArgument(params != null, "the PlaybackParams cannot be null");
                 _setPlaybackParams(params);
             }
         });
@@ -1379,7 +1371,7 @@
         addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
             @Override
             void process() {
-                Preconditions.checkNotNull(params, "the SyncParams cannot be null");
+                Preconditions.checkArgument(params != null, "the SyncParams cannot be null");
                 _setSyncParams(params);
             }
         });
@@ -4627,7 +4619,12 @@
         public void run() {
             int status = CALL_STATUS_NO_ERROR;
             try {
-                process();
+                if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+                        && getState() == PLAYER_STATE_ERROR) {
+                    status = CALL_STATUS_INVALID_OPERATION;
+                } else {
+                    process();
+                }
             } catch (IllegalStateException e) {
                 status = CALL_STATUS_INVALID_OPERATION;
             } catch (IllegalArgumentException e) {
@@ -4638,6 +4635,8 @@
                 status = CALL_STATUS_ERROR_IO;
             } catch (NoDrmSchemeException e) {
                 status = CALL_STATUS_NO_DRM_SCHEME;
+            } catch (CommandSkippedException e) {
+                status = CALL_STATUS_SKIPPED;
             } catch (Exception e) {
                 status = CALL_STATUS_ERROR_UNKNOWN;
             }
@@ -4671,4 +4670,10 @@
             }
         }
     };
+
+    private final class CommandSkippedException extends RuntimeException {
+        public CommandSkippedException(String detailMessage) {
+            super(detailMessage);
+        }
+    };
 }
diff --git a/media/jni/android_media_Media2DataSource.cpp b/media/jni/android_media_Media2DataSource.cpp
index bc3f6bd..b3434e9 100644
--- a/media/jni/android_media_Media2DataSource.cpp
+++ b/media/jni/android_media_Media2DataSource.cpp
@@ -20,12 +20,12 @@
 
 #include "android_media_Media2DataSource.h"
 
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
 #include <drm/drm_framework_common.h>
+#include <mediaplayer2/JavaVMHelper.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <nativehelper/ScopedLocalRef.h>
 
@@ -56,7 +56,7 @@
 }
 
 JMedia2DataSource::~JMedia2DataSource() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     env->DeleteGlobalRef(mMedia2DataSourceObj);
     env->DeleteGlobalRef(mByteArrayObj);
 }
@@ -75,12 +75,12 @@
         size = kBufferSize;
     }
 
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     jint numread = env->CallIntMethod(mMedia2DataSourceObj, mReadAtMethod,
             (jlong)offset, mByteArrayObj, (jint)0, (jint)size);
     if (env->ExceptionCheck()) {
         ALOGW("An exception occurred in readAt()");
-        LOGW_EX(env);
+        jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
         env->ExceptionClear();
         mJavaObjStatus = UNKNOWN_ERROR;
         return -1;
@@ -117,11 +117,11 @@
         return OK;
     }
 
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     *size = env->CallLongMethod(mMedia2DataSourceObj, mGetSizeMethod);
     if (env->ExceptionCheck()) {
         ALOGW("An exception occurred in getSize()");
-        LOGW_EX(env);
+        jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
         env->ExceptionClear();
         // After returning an error, size shouldn't be used by callers.
         *size = UNKNOWN_ERROR;
@@ -142,7 +142,7 @@
 void JMedia2DataSource::close() {
     Mutex::Autolock lock(mLock);
 
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     env->CallVoidMethod(mMedia2DataSourceObj, mCloseMethod);
     // The closed state is effectively the same as an error state.
     mJavaObjStatus = UNKNOWN_ERROR;
diff --git a/media/jni/android_media_Media2HTTPConnection.cpp b/media/jni/android_media_Media2HTTPConnection.cpp
index 60176e3..d02ee06 100644
--- a/media/jni/android_media_Media2HTTPConnection.cpp
+++ b/media/jni/android_media_Media2HTTPConnection.cpp
@@ -18,14 +18,14 @@
 #define LOG_TAG "Media2HTTPConnection-JNI"
 #include <utils/Log.h>
 
+#include <mediaplayer2/JavaVMHelper.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <nativehelper/ScopedLocalRef.h>
 
 #include "android_media_Media2HTTPConnection.h"
 #include "android_util_Binder.h"
 
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
@@ -84,7 +84,7 @@
 }
 
 JMedia2HTTPConnection::~JMedia2HTTPConnection() {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    JNIEnv *env = JavaVMHelper::getJNIEnv();
     env->DeleteGlobalRef(mMedia2HTTPConnectionObj);
     env->DeleteGlobalRef(mByteArrayObj);
 }
@@ -101,7 +101,7 @@
         }
     }
 
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     jstring juri = env->NewStringUTF(uri);
     jstring jheaders = env->NewStringUTF(tmp.string());
 
@@ -115,12 +115,12 @@
 }
 
 void JMedia2HTTPConnection::disconnect() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     env->CallVoidMethod(mMedia2HTTPConnectionObj, mDisconnectMethod);
 }
 
 ssize_t JMedia2HTTPConnection::readAt(off64_t offset, void *data, size_t size) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
 
     if (size > kBufferSize) {
         size = kBufferSize;
@@ -141,12 +141,12 @@
 }
 
 off64_t JMedia2HTTPConnection::getSize() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     return (off64_t)(env->CallLongMethod(mMedia2HTTPConnectionObj, mGetSizeMethod));
 }
 
 status_t JMedia2HTTPConnection::getMIMEType(String8 *mimeType) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     jstring jmime = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetMIMETypeMethod);
     jboolean flag = env->ExceptionCheck();
     if (flag) {
@@ -165,7 +165,7 @@
 }
 
 status_t JMedia2HTTPConnection::getUri(String8 *uri) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     jstring juri = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetUriMethod);
     jboolean flag = env->ExceptionCheck();
     if (flag) {
diff --git a/media/jni/android_media_Media2HTTPService.cpp b/media/jni/android_media_Media2HTTPService.cpp
index 382f099..1c63889 100644
--- a/media/jni/android_media_Media2HTTPService.cpp
+++ b/media/jni/android_media_Media2HTTPService.cpp
@@ -21,11 +21,11 @@
 #include "android_media_Media2HTTPConnection.h"
 #include "android_media_Media2HTTPService.h"
 
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
+#include <mediaplayer2/JavaVMHelper.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <nativehelper/ScopedLocalRef.h>
 
@@ -46,12 +46,12 @@
 }
 
 JMedia2HTTPService::~JMedia2HTTPService() {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    JNIEnv *env = JavaVMHelper::getJNIEnv();
     env->DeleteGlobalRef(mMedia2HTTPServiceObj);
 }
 
 sp<MediaHTTPConnection> JMedia2HTTPService::makeHTTPConnection() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    JNIEnv* env = JavaVMHelper::getJNIEnv();
     jobject media2HTTPConnectionObj =
         env->CallObjectMethod(mMedia2HTTPServiceObj, mMakeHTTPConnectionMethod);
 
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index abf0534..4265987 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -29,6 +29,7 @@
 #include <media/stagefright/Utils.h>
 #include <media/stagefright/foundation/ByteUtils.h>  // for FOURCC definition
 #include <mediaplayer2/JAudioTrack.h>
+#include <mediaplayer2/JavaVMHelper.h>
 #include <mediaplayer2/mediaplayer2.h>
 #include <stdio.h>
 #include <assert.h>
@@ -39,7 +40,7 @@
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 #include "android/native_window_jni.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
 #include "utils/Errors.h"  // for status_t
 #include "utils/KeyedVector.h"
 #include "utils/String8.h"
@@ -184,14 +185,14 @@
 JNIMediaPlayer2Listener::~JNIMediaPlayer2Listener()
 {
     // remove global references
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    JNIEnv *env = JavaVMHelper::getJNIEnv();
     env->DeleteGlobalRef(mObject);
     env->DeleteGlobalRef(mClass);
 }
 
 void JNIMediaPlayer2Listener::notify(int64_t srcId, int msg, int ext1, int ext2, const Parcel *obj)
 {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    JNIEnv *env = JavaVMHelper::getJNIEnv();
     if (obj && obj->dataSize() > 0) {
         jobject jParcel = createJavaParcelObject(env);
         if (jParcel != NULL) {
@@ -207,7 +208,7 @@
     }
     if (env->ExceptionCheck()) {
         ALOGW("An exception occurred while notifying an event.");
-        LOGW_EX(env);
+        jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
         env->ExceptionClear();
     }
 }
@@ -1539,8 +1540,7 @@
 // This function only registers the native methods
 static int register_android_media_MediaPlayer2Impl(JNIEnv *env)
 {
-    return AndroidRuntime::registerNativeMethods(env,
-                "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
+    return jniRegisterNativeMethods(env, "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
 }
 
 jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -1559,6 +1559,8 @@
         goto bail;
     }
 
+    JavaVMHelper::setJavaVM(vm);
+
     /* success -- return valid version number */
     result = JNI_VERSION_1_4;
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index b2be464..5ab5092 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -43,6 +43,8 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.location.Location;
 import android.location.LocationManager;
@@ -75,6 +77,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
@@ -694,6 +697,38 @@
     }
 
     /**
+     * Configure a new camera session with output surfaces and initial session parameters.
+     *
+     * @param camera The CameraDevice to be configured.
+     * @param outputSurfaces The surface list that used for camera output.
+     * @param listener The callback CameraDevice will notify when session is available.
+     * @param handler The handler used to notify callbacks.
+     * @param initialRequest Initial request settings to use as session parameters.
+     */
+    public static CameraCaptureSession configureCameraSessionWithParameters(CameraDevice camera,
+            List<Surface> outputSurfaces, BlockingSessionCallback listener,
+            Handler handler, CaptureRequest initialRequest) throws CameraAccessException {
+        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
+        for (Surface surface : outputSurfaces) {
+            outConfigurations.add(new OutputConfiguration(surface));
+        }
+        SessionConfiguration sessionConfig = new SessionConfiguration(
+                SessionConfiguration.SESSION_REGULAR, outConfigurations,
+                new HandlerExecutor(handler), listener);
+        sessionConfig.setSessionParameters(initialRequest);
+        camera.createCaptureSession(sessionConfig);
+
+        CameraCaptureSession session = listener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+        assertFalse("Camera session should not be a reprocessable session",
+                session.isReprocessable());
+        assertFalse("Capture session type must be regular",
+                CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
+                        session.getClass()));
+
+        return session;
+    }
+
+    /**
      * Configure a new camera session with output surfaces and type.
      *
      * @param camera The CameraDevice to be configured.
@@ -1334,6 +1369,20 @@
         }
     }
 
+    public static class HandlerExecutor implements Executor {
+        private final Handler mHandler;
+
+        public HandlerExecutor(Handler handler) {
+            assertNotNull("handler must be valid", handler);
+            mHandler = handler;
+        }
+
+        @Override
+        public void execute(Runnable runCmd) {
+            mHandler.post(runCmd);
+        }
+    }
+
     /**
      * Provide a mock for {@link CameraDevice.StateCallback}.
      *
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
index ddb05f0..8f27fef 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
@@ -58,6 +58,7 @@
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSessionWithParameters;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
 
@@ -872,7 +873,6 @@
             outputSurfaces.add(mReaderSurface);
         }
         mSessionListener = new BlockingSessionCallback();
-        mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
 
         CaptureRequest.Builder recordingRequestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
@@ -885,7 +885,10 @@
         }
         recordingRequestBuilder.addTarget(mRecordingSurface);
         recordingRequestBuilder.addTarget(mPreviewSurface);
-        mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
+        CaptureRequest recordingRequest = recordingRequestBuilder.build();
+        mSession = configureCameraSessionWithParameters(mCamera, outputSurfaces, mSessionListener,
+                mHandler, recordingRequest);
+        mSession.setRepeatingRequest(recordingRequest, listener, mHandler);
 
         if (useMediaRecorder) {
             mMediaRecorder.start();
diff --git a/packages/CarSystemUI/Android.mk b/packages/CarSystemUI/Android.mk
new file mode 100644
index 0000000..0d40b7f
--- /dev/null
+++ b/packages/CarSystemUI/Android.mk
@@ -0,0 +1,86 @@
+# LOCAL_PATH is not the current directory of this file.
+# If you need the local path, use CAR_SYSUI_PATH.
+# This tweak ensures that the resource overlay that is device-specific still works
+# which requires that LOCAL_PATH match the original path (which must be frameworks/base/packages/SystemUI).
+#
+# For clarity, we also define SYSTEM_UI_AOSP_PATH to frameworks/base/packages/SystemUI and refer to that
+SYSTEM_UI_AOSP_PATH := frameworks/base/packages/SystemUI
+SYSTEM_UI_CAR_PATH := frameworks/base/packages/CarSystemUI
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+# The same as SYSTEM_UI_AOSP_PATH but based on the value of LOCAL_PATH which is
+# frameworks/base/packages/CarSystemUI.
+RELATIVE_SYSTEM_UI_AOSP_PATH := ../../../../$(SYSTEM_UI_AOSP_PATH)
+
+
+LOCAL_SRC_FILES :=  \
+    $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, src) \
+    $(call all-java-files-under, $(RELATIVE_SYSTEM_UI_AOSP_PATH)/src) \
+    $(call all-Iaidl-files-under, $(RELATIVE_SYSTEM_UI_AOSP_PATH)/src)
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    SystemUIPluginLib \
+    SystemUISharedLib \
+    androidx.car_car \
+    androidx.legacy_legacy-support-v4 \
+    androidx.recyclerview_recyclerview \
+    androidx.preference_preference \
+    androidx.appcompat_appcompat \
+    androidx.mediarouter_mediarouter \
+    androidx.palette_palette \
+    androidx.legacy_legacy-preference-v14 \
+    androidx.leanback_leanback \
+    androidx.slice_slice-core \
+    androidx.slice_slice-view \
+    androidx.slice_slice-builders \
+    androidx.arch.core_core-runtime \
+    androidx.lifecycle_lifecycle-extensions \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    SystemUI-tags \
+    SystemUI-proto
+
+LOCAL_JAVA_LIBRARIES := telephony-common \
+    android.car
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(SYSTEM_UI_AOSP_PATH)/AndroidManifest.xml
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_MODULE_OWNER := google
+LOCAL_PACKAGE_NAME := CarSystemUI
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_PROGUARD_FLAG_FILES := $(RELATIVE_SYSTEM_UI_AOSP_PATH)/proguard.flags \
+    proguard.flags
+
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/res \
+    $(SYSTEM_UI_AOSP_PATH)/res-keyguard \
+    $(SYSTEM_UI_AOSP_PATH)/res
+
+ifneq ($(INCREMENTAL_BUILDS),)
+    LOCAL_PROGUARD_ENABLED := disabled
+    LOCAL_JACK_ENABLED := incremental
+endif
+
+LOCAL_DX_FLAGS := --multi-dex
+LOCAL_JACK_FLAGS := --multi-dex native
+
+include frameworks/base/packages/SettingsLib/common.mk
+
+LOCAL_OVERRIDES_PACKAGES := SystemUI
+
+LOCAL_AAPT_FLAGS := --extra-packages com.android.keyguard
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under, $(SYSTEM_UI_CAR_PATH))
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
new file mode 100644
index 0000000..4e8a3a3
--- /dev/null
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        package="com.android.systemui"
+        android:sharedUserId="android.uid.systemui"
+        coreApp="true">
+
+
+</manifest>
diff --git a/packages/CarSystemUI/proguard.flags b/packages/CarSystemUI/proguard.flags
new file mode 100644
index 0000000..ceb037c
--- /dev/null
+++ b/packages/CarSystemUI/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.systemui.CarSystemUIFactory
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/res/drawable/car_ic_apps.xml
new file mode 100644
index 0000000..13b543b
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="33dp"
+    android:height="33dp"
+    android:viewportHeight="33.0"
+    android:viewportWidth="33.0">
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="evenOdd"
+        android:pathData="M1,20L12,20C12.55,20 13,20.45 13,21L13,32C13,32.55 12.55,33 12,33L1,33C0.45,33 0,32.55 0,32L0,21C0,20.45 0.45,20 1,20ZM21,20L32,20C32.55,20 33,20.45 33,21L33,32C33,32.55 32.55,33 32,33L21,33C20.45,33 20,32.55 20,32L20,21C20,20.45 20.45,20 21,20ZM1,0L12,0C12.55,0 13,0.45 13,1L13,12C13,12.55 12.55,13 12,13L1,13C0.45,13 0,12.55 0,12L0,1C0,0.45 0.45,0 1,0ZM21,0L32,0C32.55,0 33,0.45 33,1L33,12C33,12.55 32.55,13 32,13L21,13C20.45,13 20,12.55 20,12L20,1C20,0.45 20.45,0 21,0Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml b/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml
new file mode 100644
index 0000000..498d366
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="33dp"
+    android:height="33dp"
+    android:viewportHeight="33.0"
+    android:viewportWidth="33.0">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="evenOdd"
+        android:pathData="M1,20L12,20C12.55,20 13,20.45 13,21L13,32C13,32.55 12.55,33 12,33L1,33C0.45,33 0,32.55 0,32L0,21C0,20.45 0.45,20 1,20ZM21,20L32,20C32.55,20 33,20.45 33,21L33,32C33,32.55 32.55,33 32,33L21,33C20.45,33 20,32.55 20,32L20,21C20,20.45 20.45,20 21,20ZM1,0L12,0C12.55,0 13,0.45 13,1L13,12C13,12.55 12.55,13 12,13L1,13C0.45,13 0,12.55 0,12L0,1C0,0.45 0.45,0 1,0ZM21,0L32,0C32.55,0 33,0.45 33,1L33,12C33,12.55 32.55,13 32,13L21,13C20.45,13 20,12.55 20,12L20,1C20,0.45 20.45,0 21,0Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml
new file mode 100644
index 0000000..250bfe8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+    <item android:drawable="@drawable/car_ic_apps_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music.xml b/packages/CarSystemUI/res/drawable/car_ic_music.xml
new file mode 100644
index 0000000..8c15494
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="34dp"
+    android:height="39dp"
+    android:viewportHeight="39.0"
+    android:viewportWidth="34.0">
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="evenOdd"
+        android:pathData="M23,25L31,23L31,9L12,12L12,38C12,38.45 11.45,39 11,39L1,39C0.55,39 0.01,38.87 0,38.3C0,38.3 0,35.87 0,31C0,30.42 0.62,30.08 1,30L9,28L9,4.85C9,4.36 9.36,3.94 9.84,3.87L32.84,0.19C33.39,0.1 33.9,0.47 33.99,1.01C34,1.07 34,1.12 34,1.17L34,33.06C34,33.51 33.45,34 33,34L23,34C22.55,34 22,33.51 22,33.06L22,26C22,25.61 22.16,25.18 23,25Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="1"/>
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="evenOdd"
+        android:pathData="M23.57,26.48L23.57,32.42L32.42,32.42L32.42,24.27L23.57,26.48ZM32.42,7.18L32.42,1.85L10.57,5.34L10.57,10.63L32.42,7.18ZM10.42,29.27L9.38,29.53L1.58,31.48C1.58,34.35 1.58,34.45 1.57,36.48C1.58,36.89 1.58,37.2 1.58,37.43L10.42,37.43L10.42,29.27Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="3.15"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music_black.xml b/packages/CarSystemUI/res/drawable/car_ic_music_black.xml
new file mode 100644
index 0000000..64287a5
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music_black.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="34dp"
+    android:height="39dp"
+    android:viewportHeight="39.0"
+    android:viewportWidth="34.0">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="evenOdd"
+        android:pathData="M23,25L31,23L31,9L12,12L12,38C12,38.45 11.45,39 11,39L1,39C0.55,39 0.01,38.87 0,38.3C0,38.3 0,35.87 0,31C0,30.42 0.62,30.08 1,30L9,28L9,4.85C9,4.36 9.36,3.94 9.84,3.87L32.84,0.19C33.39,0.1 33.9,0.47 33.99,1.01C34,1.07 34,1.12 34,1.17L34,33.06C34,33.51 33.45,34 33,34L23,34C22.55,34 22,33.51 22,33.06L22,26C22,25.61 22.16,25.18 23,25Z"
+        android:strokeColor="#FF000000"
+        android:strokeWidth="1"/>
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="evenOdd"
+        android:pathData="M23.57,26.48L23.57,32.42L32.42,32.42L32.42,24.27L23.57,26.48ZM32.42,7.18L32.42,1.85L10.57,5.34L10.57,10.63L32.42,7.18ZM10.42,29.27L9.38,29.53L1.58,31.48C1.58,34.35 1.58,34.45 1.57,36.48C1.58,36.89 1.58,37.2 1.58,37.43L10.42,37.43L10.42,29.27Z"
+        android:strokeColor="#FF000000"
+        android:strokeWidth="3.15"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml
new file mode 100644
index 0000000..9703a8c
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+    <item android:drawable="@drawable/car_ic_music_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation.xml
new file mode 100644
index 0000000..58667be
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="37dp"
+    android:viewportHeight="37.0"
+    android:viewportWidth="32.0">
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="evenOdd"
+        android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml
new file mode 100644
index 0000000..145d4c9
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="37dp"
+    android:viewportHeight="37.0"
+    android:viewportWidth="32.0">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="evenOdd"
+        android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml
new file mode 100644
index 0000000..f0174db
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+    <item android:drawable="@drawable/car_ic_navigation_black" android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification.xml b/packages/CarSystemUI/res/drawable/car_ic_notification.xml
new file mode 100644
index 0000000..f96b050
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="38dp"
+    android:viewportHeight="38.0"
+    android:viewportWidth="32.0">
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="evenOdd"
+        android:pathData="M20.62,3.98C25.51,5.85 28.98,10.61 28.98,16.18L28.98,24.95L32,24.95L32,31.22L0,31.22L0,24.95L3.02,24.95L3.02,16.18C3.02,10.61 6.49,5.85 11.38,3.98C11.72,1.73 13.66,0 16,0C18.34,0 20.28,1.73 20.62,3.98ZM11.33,33.3L20.67,33.3C20.67,35.9 18.58,38 16,38C13.42,38 11.33,35.9 11.33,33.3Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml
new file mode 100644
index 0000000..5f8df88
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="38dp"
+    android:viewportHeight="38.0"
+    android:viewportWidth="32.0">
+    <path
+        android:fillColor="#000000"
+        android:fillType="evenOdd"
+        android:pathData="M20.62,3.98C25.51,5.85 28.98,10.61 28.98,16.18L28.98,24.95L32,24.95L32,31.22L0,31.22L0,24.95L3.02,24.95L3.02,16.18C3.02,10.61 6.49,5.85 11.38,3.98C11.72,1.73 13.66,0 16,0C18.34,0 20.28,1.73 20.62,3.98ZM11.33,33.3L20.67,33.3C20.67,35.9 18.58,38 16,38C13.42,38 11.33,35.9 11.33,33.3Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
new file mode 100644
index 0000000..02eec93
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+    <item android:drawable="@drawable/car_ic_notification_black" android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/res/drawable/car_ic_overview.xml
new file mode 100644
index 0000000..590109a
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="39dp"
+    android:height="40dp"
+    android:viewportHeight="40.0"
+    android:viewportWidth="39.0">
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="evenOdd"
+        android:pathData="M33.4,22.74L33.4,37.74C33.4,38.84 32.51,39.74 31.4,39.74L24.4,39.74L24.4,24.74L14.4,24.74L14.4,39.74L7.4,39.74C6.3,39.74 5.4,38.84 5.4,37.74L5.4,22.74L0.5,22.74C0.22,22.74 0,22.51 0,22.24C0,22.12 0.04,22 0.12,21.91L19.03,0.17C19.21,-0.04 19.52,-0.06 19.73,0.12C19.75,0.14 19.76,0.15 19.78,0.17L38.68,21.91C38.86,22.12 38.84,22.43 38.63,22.62C38.54,22.69 38.43,22.74 38.31,22.74L33.4,22.74Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml b/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml
new file mode 100644
index 0000000..48cff97
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="39dp"
+    android:height="40dp"
+    android:viewportHeight="40.0"
+    android:viewportWidth="39.0">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="evenOdd"
+        android:pathData="M33.4,22.74L33.4,37.74C33.4,38.84 32.51,39.74 31.4,39.74L24.4,39.74L24.4,24.74L14.4,24.74L14.4,39.74L7.4,39.74C6.3,39.74 5.4,38.84 5.4,37.74L5.4,22.74L0.5,22.74C0.22,22.74 0,22.51 0,22.24C0,22.12 0.04,22 0.12,21.91L19.03,0.17C19.21,-0.04 19.52,-0.06 19.73,0.12C19.75,0.14 19.76,0.15 19.78,0.17L38.68,21.91C38.86,22.12 38.84,22.43 38.63,22.62C38.54,22.69 38.43,22.74 38.31,22.74L33.4,22.74Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml
new file mode 100644
index 0000000..18ebf0c
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+    <item android:drawable="@drawable/car_ic_overview_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/res/drawable/car_ic_phone.xml
new file mode 100644
index 0000000..7091670
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="40dp"
+    android:viewportHeight="40.0"
+    android:viewportWidth="40.0">
+    <path
+        android:fillColor="@color/car_nav_icon_fill_color"
+        android:fillType="nonZero"
+        android:pathData="M21.31,35.75L21.3,35.76L3.46,17.92L0.03,6.25C-0.16,5.32 0.53,3.88 1.41,3.51C6.36,1.25 8.88,0.1 8.95,0.08C10.02,-0.23 11.39,0.39 11.7,1.45L13.76,10.37C13.97,11.1 13.62,11.91 13.07,12.43L8.24,17.25L22.31,31.32L27.13,26.49C27.65,25.94 28.47,25.6 29.19,25.81L38.11,27.87C39.18,28.17 39.79,29.55 39.49,30.61C39.46,30.69 38.32,33.2 36.05,38.16C35.68,39.04 34.25,39.73 33.31,39.53L21.65,36.1C21.51,35.96 21.4,35.84 21.31,35.75Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml b/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml
new file mode 100644
index 0000000..087eebb
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="40dp"
+    android:viewportHeight="40.0"
+    android:viewportWidth="40.0">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="nonZero"
+        android:pathData="M21.31,35.75L21.3,35.76L3.46,17.92L0.03,6.25C-0.16,5.32 0.53,3.88 1.41,3.51C6.36,1.25 8.88,0.1 8.95,0.08C10.02,-0.23 11.39,0.39 11.7,1.45L13.76,10.37C13.97,11.1 13.62,11.91 13.07,12.43L8.24,17.25L22.31,31.32L27.13,26.49C27.65,25.94 28.47,25.6 29.19,25.81L38.11,27.87C39.18,28.17 39.79,29.55 39.49,30.61C39.46,30.69 38.32,33.2 36.05,38.16C35.68,39.04 34.25,39.73 33.31,39.53L21.65,36.1C21.51,35.96 21.4,35.84 21.31,35.75Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml
new file mode 100644
index 0000000..197eac0
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+    <item android:drawable="@drawable/car_ic_phone_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml b/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml
new file mode 100644
index 0000000..781beaf
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="70dp"
+    android:height="70dp"
+    android:viewportHeight="70.0"
+    android:viewportWidth="70.0">
+    <path
+        android:fillColor="@color/car_accent"
+        android:fillType="evenOdd"
+        android:pathData="M4,0L66,0A4,4 0,0 1,70 4L70,66A4,4 0,0 1,66 70L4,70A4,4 0,0 1,0 66L0,4A4,4 0,0 1,4 0z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml b/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml
new file mode 100644
index 0000000..1a9b8a5
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <padding
+                android:bottom="@dimen/car_padding_1"
+                android:left="@dimen/car_padding_1"
+                android:right="@dimen/car_padding_1"
+                android:top="@dimen/car_padding_1"/>
+            <solid android:color="@android:color/black"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/car_accent"/>
+            <size
+                android:width="@dimen/car_seekbar_thumb_size"
+                android:height="@dimen/car_seekbar_thumb_size"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/nav_button_background.xml b/packages/CarSystemUI/res/drawable/nav_button_background.xml
new file mode 100644
index 0000000..376347c
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/nav_button_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/nav_bar_ripple_background_color">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:colorAccent"/>
+            <corners android:radius="6dp"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/notification_material_bg.xml b/packages/CarSystemUI/res/drawable/notification_material_bg.xml
new file mode 100644
index 0000000..03746c8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/notification_material_bg.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/notification_ripple_untinted_color">
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <solid android:color="@color/notification_material_background_color"/>
+            <corners
+                android:radius="@dimen/notification_shadow_radius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml b/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml
new file mode 100644
index 0000000..03746c8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/notification_ripple_untinted_color">
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <solid android:color="@color/notification_material_background_color"/>
+            <corners
+                android:radius="@dimen/notification_shadow_radius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/volume_dialog_background.xml b/packages/CarSystemUI/res/drawable/volume_dialog_background.xml
new file mode 100644
index 0000000..fa3ca8f
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/volume_dialog_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="?android:attr/colorBackgroundFloating"/>
+    <padding
+        android:bottom="5dp"
+        android:left="5dp"
+        android:right="5dp"
+        android:top="5dp"/>
+    <corners android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"/>
+</shape>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..c7dfa9b
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black"
+    android:orientation="vertical">
+    <LinearLayout
+        android:id="@id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingStart="20dp"
+        android:paddingEnd="20dp"
+        android:gravity="center">
+
+        <com.android.systemui.statusbar.car.CarFacetButton
+            android:id="@+id/home"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+            systemui:icon="@drawable/car_ic_overview"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+            systemui:longIntent="intent:#Intent;action=com.google.android.demandspace.START;end"
+            systemui:selectedIcon="@drawable/car_ic_overview_selected"
+            systemui:useMoreIcon="false"
+        />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"/>
+
+        <com.android.systemui.statusbar.car.CarFacetButton
+            android:id="@+id/maps_nav"
+            style="@style/NavigationBarButton"
+            systemui:categories="android.intent.category.APP_MAPS"
+            systemui:icon="@drawable/car_ic_navigation"
+            systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.CarLauncher;category=android.intent.category.APP_MAPS;launchFlags=0x24000000;end"
+            systemui:selectedIcon="@drawable/car_ic_navigation_selected"
+            systemui:useMoreIcon="false"
+        />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"/>
+
+        <com.android.systemui.statusbar.car.CarFacetButton
+            android:id="@+id/music_nav"
+            style="@style/NavigationBarButton"
+            systemui:icon="@drawable/car_ic_music"
+            systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end"
+            systemui:packages="com.android.car.media"
+            systemui:selectedIcon="@drawable/car_ic_music_selected"
+            systemui:useMoreIcon="false"
+        />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"/>
+
+        <com.android.systemui.statusbar.car.CarFacetButton
+            android:id="@+id/phone_nav"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.dialer/.TelecomActivity"
+            systemui:icon="@drawable/car_ic_phone"
+            systemui:intent="intent:#Intent;component=com.android.car.dialer/.TelecomActivity;launchFlags=0x14000000;end"
+            systemui:selectedIcon="@drawable/car_ic_phone_selected"
+            systemui:useMoreIcon="false"
+        />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"/>
+
+        <com.android.systemui.statusbar.car.CarFacetButton
+            android:id="@+id/grid_nav"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+            systemui:icon="@drawable/car_ic_apps"
+            systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+            systemui:selectedIcon="@drawable/car_ic_apps_selected"
+            systemui:useMoreIcon="false"
+        />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"/>
+
+        <com.android.keyguard.AlphaOptimizedImageButton
+            android:id="@+id/notifications"
+            style="@style/NavigationBarButton"
+            android:background="?android:attr/selectableItemBackground"
+            android:src="@drawable/car_ic_notification"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/lock_screen_nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingStart="@dimen/car_keyline_1"
+        android:paddingEnd="@dimen/car_keyline_1"
+        android:gravity="center"
+        android:visibility="gone">
+    </LinearLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
new file mode 100644
index 0000000..46e60db
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingStart="@dimen/car_padding_5"
+        android:paddingEnd="@dimen/car_padding_5">
+
+        <com.android.systemui.statusbar.car.CarNavigationButton
+            android:id="@+id/home"
+            android:layout_width="@dimen/car_touch_target_size"
+            android:layout_height="match_parent"
+            android:background="?android:attr/selectableItemBackground"
+            android:src="@drawable/car_ic_overview"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+        />
+    </LinearLayout>
+</com.android.systemui.statusbar.car.CarNavigationBarView>
+
diff --git a/packages/CarSystemUI/res/layout/car_status_bar_header.xml b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
new file mode 100644
index 0000000..e446072
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<!-- Extends LinearLayout -->
+<com.android.systemui.qs.car.CarStatusBarHeader
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/header"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/status_bar_height">
+
+    <include layout="@layout/car_top_navigation_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+    />
+</com.android.systemui.qs.car.CarStatusBarHeader>
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
new file mode 100644
index 0000000..55d50a0
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/car_top_bar"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black"
+    android:orientation="vertical">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1">
+
+        <FrameLayout
+            android:id="@+id/left_hvac_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentStart="true"
+        >
+
+            <com.android.systemui.statusbar.car.CarNavigationButton
+                android:id="@+id/hvacleft"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:broadcast="true"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+            />
+
+            <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+                android:id="@+id/lefttext"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="@dimen/car_padding_4"
+                android:paddingEnd="16dp"
+                android:gravity="center_vertical|start"
+                android:minEms="4"
+                android:textAppearance="@style/TextAppearance.Car.Status"
+                systemui:hvacAreaId="1"
+                systemui:hvacMaxText="@string/hvac_max_text"
+                systemui:hvacMaxValue="@dimen/hvac_max_value"
+                systemui:hvacMinText="@string/hvac_min_text"
+                systemui:hvacMinValue="@dimen/hvac_min_value"
+                systemui:hvacPivotOffset="60dp"
+                systemui:hvacPropertyId="358614275"
+                systemui:hvacTempFormat="%.0f\u00B0"
+            />
+        </FrameLayout>
+
+        <FrameLayout
+            android:id="@+id/clock_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_centerInParent="true"
+        >
+            <com.android.systemui.statusbar.car.CarNavigationButton
+                android:id="@+id/qs"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivity;launchFlags=0x14008000;end"
+            />
+            <com.android.systemui.statusbar.policy.Clock
+                android:id="@+id/clock"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:elevation="5dp"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+            />
+        </FrameLayout>
+
+        <LinearLayout
+            android:id="@+id/system_icon_area"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:layout_toEndOf="@+id/clock_container"
+            android:paddingStart="@dimen/car_padding_1"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+        >
+
+            <include
+                layout="@layout/system_icons"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:paddingStart="4dp"
+                android:gravity="center_vertical"
+            />
+        </LinearLayout>
+
+        <FrameLayout
+            android:id="@+id/right_hvac_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentEnd="true"
+        >
+
+            <com.android.systemui.statusbar.car.CarNavigationButton
+                android:id="@+id/hvacright"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:broadcast="true"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+            />
+
+            <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+                android:id="@+id/righttext"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="16dp"
+                android:paddingEnd="@dimen/car_padding_4"
+                android:gravity="center_vertical|end"
+                android:minEms="4"
+                android:textAppearance="@style/TextAppearance.Car.Status"
+                systemui:hvacAreaId="4"
+                systemui:hvacMaxText="@string/hvac_max_text"
+                systemui:hvacMaxValue="@dimen/hvac_max_value"
+                systemui:hvacMinText="@string/hvac_min_text"
+                systemui:hvacMinValue="@dimen/hvac_min_value"
+                systemui:hvacPivotOffset="60dp"
+                systemui:hvacPropertyId="358614275"
+                systemui:hvacTempFormat="%.0f\u00B0"
+            />
+        </FrameLayout>
+    </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_volume_dialog.xml b/packages/CarSystemUI/res/layout/car_volume_dialog.xml
new file mode 100644
index 0000000..c98740e
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_volume_dialog.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<androidx.car.widget.PagedListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/volume_list"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@android:color/black"
+    android:minWidth="@dimen/volume_dialog_panel_width"
+    android:theme="@style/Theme.Car.DialogListView"
+    app:dividerEndMargin="@dimen/car_keyline_1"
+    app:dividerStartMargin="@dimen/car_keyline_1"
+    app:gutter="none"
+    app:scrollBarEnabled="false"
+    app:showPagedListViewDivider="true"/>
diff --git a/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml b/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml
new file mode 100644
index 0000000..2793e56
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<com.android.systemui.statusbar.StatusBarWifiView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/wifi_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical">
+
+    <com.android.keyguard.AlphaOptimizedLinearLayout
+        android:id="@+id/wifi_group"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingStart="2dp"
+        android:gravity="center_vertical"
+    >
+        <FrameLayout
+            android:id="@+id/inout_container"
+            android:layout_width="wrap_content"
+            android:layout_height="17dp"
+            android:gravity="center_vertical">
+            <ImageView
+                android:id="@+id/wifi_in"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingEnd="2dp"
+                android:src="@drawable/ic_activity_down"
+                android:visibility="gone"
+            />
+            <ImageView
+                android:id="@+id/wifi_out"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingEnd="2dp"
+                android:src="@drawable/ic_activity_up"
+                android:visibility="gone"
+            />
+        </FrameLayout>
+        <FrameLayout
+            android:id="@+id/wifi_combo"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical">
+            <com.android.systemui.statusbar.AlphaOptimizedImageView
+                android:id="@+id/wifi_signal"
+                android:layout_width="@dimen/status_bar_icon_size"
+                android:layout_height="@dimen/status_bar_icon_size"
+                android:theme="?attr/lightIconTheme"/>
+        </FrameLayout>
+
+        <View
+            android:id="@+id/wifi_signal_spacer"
+            android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
+            android:layout_height="4dp"
+            android:visibility="gone"/>
+
+        <ViewStub
+            android:id="@+id/connected_device_signals_stub"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout="@layout/connected_device_signal"/>
+
+        <View
+            android:id="@+id/wifi_airplane_spacer"
+            android:layout_width="@dimen/status_bar_airplane_spacer_width"
+            android:layout_height="4dp"
+            android:visibility="gone"
+        />
+    </com.android.keyguard.AlphaOptimizedLinearLayout>
+</com.android.systemui.statusbar.StatusBarWifiView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
new file mode 100644
index 0000000..0594dce
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<!-- This is the combined status bar / notification panel window. -->
+<com.android.systemui.statusbar.phone.StatusBarWindowView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <com.android.systemui.statusbar.BackDropView
+        android:id="@+id/backdrop"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        sysui:ignoreRightInset="true"
+    >
+        <ImageView android:id="@+id/backdrop_back"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"/>
+        <ImageView android:id="@+id/backdrop_front"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"
+            android:visibility="invisible"/>
+    </com.android.systemui.statusbar.BackDropView>
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_behind"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/status_bar_height"
+        android:orientation="vertical"
+    >
+        <FrameLayout
+            android:id="@+id/status_bar_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+        />
+
+        <include layout="@layout/car_top_navigation_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+        />
+    </LinearLayout>
+
+    <include layout="@layout/brightness_mirror"/>
+
+    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/car_fullscreen_user_switcher"/>
+
+    <include layout="@layout/status_bar_expanded"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"/>
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_in_front"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+</com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/CarSystemUI/res/layout/system_icons.xml b/packages/CarSystemUI/res/layout/system_icons.xml
new file mode 100644
index 0000000..a7dd65e
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/system_icons.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/system_icons"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical">
+
+    <com.android.systemui.statusbar.phone.StatusIconContainer
+        android:id="@+id/statusIcons"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:paddingEnd="4dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+    />
+
+    <com.android.systemui.BatteryMeterView
+        android:id="@+id/battery"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+    />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/values-night/colors.xml b/packages/CarSystemUI/res/values-night/colors.xml
new file mode 100644
index 0000000..dad94a8
--- /dev/null
+++ b/packages/CarSystemUI/res/values-night/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="car_accent">#356FE5</color>
+    <color name="status_bar_background_color">#ff000000</color>
+    <color name="system_bar_background_opaque">#ff0c1013</color>
+
+    <!-- The color of the ripples on the untinted notifications -->
+    <color name="notification_ripple_untinted_color">@color/ripple_material_dark</color>
+</resources>
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
new file mode 100644
index 0000000..6178738
--- /dev/null
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 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.
+  -->
+
+<resources>
+
+    <!-- Custom attributes to configure hvac values -->
+    <declare-styleable name="AnimatedTemperatureView">
+        <attr name="hvacAreaId" format="integer"/>
+        <attr name="hvacPropertyId" format="integer"/>
+        <attr name="hvacTempFormat" format="string"/>
+        <!-- how far away the animations should center around -->
+        <attr name="hvacPivotOffset" format="dimension"/>
+        <attr name="hvacMinValue" format="float"/>
+        <attr name="hvacMaxValue" format="float"/>
+        <attr name="hvacMinText" format="string|reference"/>
+        <attr name="hvacMaxText" format="string|reference"/>
+        <attr name="android:gravity"/>
+        <attr name="android:minEms"/>
+        <attr name="android:textAppearance"/>
+    </declare-styleable>
+</resources>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
new file mode 100644
index 0000000..e9ddbaf
--- /dev/null
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="nav_bar_ripple_background_color">#40ffffff</color>
+    <color name="car_accent">#356FE5</color>
+    <!-- colors for user switcher -->
+    <color name="car_user_switcher_background_color">#000000</color>
+    <color name="car_user_switcher_name_text_color">@color/car_title2_light</color>
+    <color name="car_user_switcher_add_user_background_color">#131313</color>
+    <color name="car_nav_icon_fill_color">@color/car_grey_50</color>
+    <!-- colors for seekbar -->
+    <color name="car_seekbar_track_background">#131315</color>
+    <color name="car_seekbar_track_secondary_progress">@color/car_accent</color>
+    <!-- colors for volume dialog tint -->
+    <color name="car_volume_dialog_tint">@color/car_tint_light</color>
+
+    <!-- System ui can't depend on car libs so redefine. -->
+    <color name="car_grey_50">#fffafafa</color>
+
+    <color name="docked_divider_background">@color/car_grey_50</color>
+    <color name="system_bar_background_opaque">#ff172026</color>
+
+    <color name="status_bar_background_color">#33000000</color>
+    <drawable name="system_bar_background">@color/status_bar_background_color</drawable>
+
+    <!-- The scrim color for the background of the notifications shade. -->
+    <color name="scrim_behind_color">#172026</color>
+
+    <!-- The color of the dividing line between grouped notifications. -->
+    <color name="notification_divider_color">@*android:color/notification_action_list</color>
+
+    <!-- The color of the ripples on the untinted notifications -->
+    <color name="notification_ripple_untinted_color">@color/ripple_material_light</color>
+
+    <color name="car_teal_700">#ff00796b</color>
+    <color name="car_grey_300">#ffe0e0e0</color>
+    <color name="car_grey_900">#ff212121</color>
+
+    <color name="keyguard_button_text_color">@android:color/black</color>
+</resources>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
new file mode 100644
index 0000000..452d61d
--- /dev/null
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<resources>
+    <string name="config_statusBarComponent" translatable="false">
+        com.android.systemui.statusbar.car.CarStatusBar
+    </string>
+    <string name="config_systemUIFactoryComponent" translatable="false">
+        com.android.systemui.CarSystemUIFactory
+    </string>
+    <bool name="config_enableFullscreenUserSwitcher">true</bool>
+
+    <!-- configure which system ui bars should be displayed -->
+    <bool name="config_enableLeftNavigationBar">false</bool>
+    <bool name="config_enableRightNavigationBar">false</bool>
+    <bool name="config_enableBottomNavigationBar">true</bool>
+
+</resources>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
new file mode 100644
index 0000000..3829aa3
--- /dev/null
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<resources>
+    <!--
+       Note: status bar height and navigation bar heights are defined
+       in frameworks/base/core package and thus will have no effect if
+       set here. See car_product overlay for car specific defaults-->
+
+    <dimen name="status_bar_icon_drawing_size_dark">36dp</dimen>
+    <dimen name="status_bar_icon_drawing_size">36dp</dimen>
+    <dimen name="car_qs_header_system_icons_area_height">96dp</dimen>
+    <!-- The amount by which to scale up the status bar icons. -->
+    <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.75</item>
+
+    <dimen name="car_primary_icon_size">36dp</dimen>
+
+    <!-- dimensions for the car user switcher -->
+    <dimen name="car_user_switcher_name_text_size">@dimen/car_title2_size</dimen>
+    <dimen name="car_user_switcher_vertical_spacing_between_users">124dp</dimen>
+
+    <!--These values represent MIN and MAX for hvac-->
+    <item name="hvac_min_value" format="float" type="dimen">0</item>
+    <item name="hvac_max_value" format="float" type="dimen">126</item>
+
+    <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or
+         quick settings header -->
+    <dimen name="max_avatar_size">128dp</dimen>
+
+    <!-- Standard image button size for volume dialog buttons -->
+    <dimen name="volume_button_size">84dp</dimen>
+    <!-- The maximum width allowed for the volume dialog. For auto, we allow this to span a good
+         deal of the screen. This value accounts for the side margins. -->
+    <dimen name="volume_dialog_panel_width">1920dp</dimen>
+    <dimen name="volume_dialog_side_margin">@dimen/side_margin</dimen>
+
+    <dimen name="volume_dialog_elevation">6dp</dimen>
+
+    <dimen name="volume_dialog_row_margin_end">@dimen/car_keyline_3</dimen>
+
+    <dimen name="volume_dialog_row_padding_end">0dp</dimen>
+
+    <dimen name="line_item_height">128dp</dimen>
+    <dimen name="volume_icon_size">96dp</dimen>
+    <dimen name="side_margin">148dp</dimen>
+    <dimen name="car_keyline_1">24dp</dimen>
+    <dimen name="car_keyline_2">96dp</dimen>
+    <dimen name="car_keyline_3">128dp</dimen>
+</resources>
diff --git a/packages/CarSystemUI/res/values/integers.xml b/packages/CarSystemUI/res/values/integers.xml
new file mode 100644
index 0000000..8b87c74
--- /dev/null
+++ b/packages/CarSystemUI/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (c) 2018, 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.
+-->
+
+<resources>
+    <integer name="user_fullscreen_switcher_num_col">2</integer>
+</resources>
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
new file mode 100644
index 0000000..0368e61
--- /dev/null
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2018 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.
+  -->
+
+<resources>
+    <!-- String to represent lowest setting of an HVAC system [CHAR LIMIT=5]-->
+    <string name="hvac_min_text">Min</string>
+    <!-- String to represent largest setting of an HVAC system [CHAR LIMIT=5]-->
+    <string name="hvac_max_text">Max</string>
+</resources>
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
new file mode 100644
index 0000000..22a699c
--- /dev/null
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- The style for the volume icons in the volume dialog. This style makes the icon scale to
+         fit its container since auto wants the icon to be larger. The padding is added to make it
+         so the icon does not press along the edges of the dialog. -->
+    <style name="VolumeButtons" parent="@android:style/Widget.Material.Button.Borderless">
+        <item name="android:background">@drawable/btn_borderless_rect</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:padding">22dp</item>
+    </style>
+
+    <style name="TextAppearance.StatusBar.Clock"
+        parent="@*android:style/TextAppearance.StatusBar.Icon">
+        <item name="android:textSize">42sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+        <item name="android:textColor">@color/car_grey_50</item>
+    </style>
+
+    <style name="TextAppearance.Car.Status">
+        <item name="android:textSize">@dimen/car_body2_size</item>
+        <item name="android:textColor">@color/car_grey_50</item>
+    </style>
+
+    <style name="CarNavigationBarButtonTheme">
+        <item name="android:colorControlHighlight">@color/nav_bar_ripple_background_color</item>
+    </style>
+
+    <style name="Theme.Car.DialogListView" parent="@style/Theme.Car.NoActionBar">
+        <item name="listItemBackgroundColor">@android:color/black</item>
+    </style>
+
+    <style name="NavigationBarButton">
+        <item name="android:layout_height">96dp</item>
+        <item name="android:layout_width">96dp</item>
+        <item name="android:background">@drawable/nav_button_background</item>
+    </style>
+
+</resources>
diff --git a/packages/CarSystemUI/res/xml/car_volume_items.xml b/packages/CarSystemUI/res/xml/car_volume_items.xml
new file mode 100644
index 0000000..8715946
--- /dev/null
+++ b/packages/CarSystemUI/res/xml/car_volume_items.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 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.
+  -->
+
+<!--
+  Defines all possible items on car volume settings UI, keyed by usage.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+  <item car:usage="unknown"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="media"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="voice_communication"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="voice_communication_signalling"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="alarm"
+        car:icon="@drawable/ic_volume_alarm"/>
+  <item car:usage="notification"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_ringtone"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_request"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_instant"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_delayed"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_event"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="assistance_accessibility"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="assistance_navigation_guidance"
+        car:icon="@drawable/car_ic_navigation"/>
+  <item car:usage="assistance_sonification"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="game"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="assistant"
+        car:icon="@drawable/car_ic_music"/>
+</carVolumeItems>
+
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
new file mode 100644
index 0000000..1d39f72
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.util.ArrayMap;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.car.CarNotificationEntryManager;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.car.CarFacetButtonController;
+import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+/**
+ * Class factory to provide car specific SystemUI components.
+ */
+public class CarSystemUIFactory extends SystemUIFactory {
+
+    public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
+        ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
+        return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
+    }
+
+    @Override
+    public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
+        Context context) {
+        super.injectDependencies(providers, context);
+        providers.put(NotificationEntryManager.class,
+            () -> new CarNotificationEntryManager(context));
+        providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context));
+        providers.put(HvacController.class, () -> new HvacController(context));
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
new file mode 100644
index 0000000..27d3106
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import com.android.systemui.statusbar.car.hvac.TemperatureView;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
+/**
+ * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in
+ * the XML.
+ * XML properties:
+ * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385)
+ * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
+ * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol)
+ * hvacOrientaion = Example: left
+ * <p>
+ * Note: It registers itself with {@link HvacController}
+ */
+public class AnimatedTemperatureView extends FrameLayout implements TemperatureView {
+
+    private static final float TEMPERATURE_EQUIVALENT_DELTA = .01f;
+    private static final Property<ColorDrawable, Integer> COLOR_PROPERTY =
+            new Property<ColorDrawable, Integer>(Integer.class, "color") {
+
+                @Override
+                public Integer get(ColorDrawable object) {
+                    return object.getColor();
+                }
+
+                @Override
+                public void set(ColorDrawable object, Integer value) {
+                    object.setColor(value);
+                }
+            };
+
+    static boolean isHorizontal(int gravity) {
+        return Gravity.isHorizontal(gravity)
+                && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.CENTER_HORIZONTAL;
+    }
+
+    @SuppressLint("RtlHardcoded")
+    static boolean isLeft(int gravity, int layoutDirection) {
+        return Gravity
+                .getAbsoluteGravity(gravity & Gravity.HORIZONTAL_GRAVITY_MASK, layoutDirection)
+                == Gravity.LEFT;
+    }
+
+    static boolean isVertical(int gravity) {
+        return Gravity.isVertical(gravity)
+                && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.CENTER_VERTICAL;
+    }
+
+    static boolean isTop(int gravity) {
+        return (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP;
+    }
+
+    private final int mAreaId;
+    private final int mPropertyId;
+    private final int mPivotOffset;
+    private final int mGravity;
+    private final int mTextAppearanceRes;
+    private final int mMinEms;
+    private final Rect mPaddingRect;
+    private final float mMinValue;
+    private final float mMaxValue;
+
+    private final ColorDrawable mBackgroundColor;
+
+    private final TemperatureColorStore mColorStore = new TemperatureColorStore();
+    private final TemperatureBackgroundAnimator mBackgroundAnimator;
+    private final TemperatureTextAnimator mTextAnimator;
+
+    public AnimatedTemperatureView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray typedArray = context.obtainStyledAttributes(attrs,
+                R.styleable.AnimatedTemperatureView);
+        mAreaId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacAreaId, -1);
+        mPropertyId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacPropertyId, -1);
+        mPivotOffset =
+                typedArray.getDimensionPixelOffset(
+                        R.styleable.AnimatedTemperatureView_hvacPivotOffset, 0);
+        mGravity = typedArray.getInt(R.styleable.AnimatedTemperatureView_android_gravity,
+                Gravity.START);
+        mTextAppearanceRes =
+                typedArray.getResourceId(R.styleable.AnimatedTemperatureView_android_textAppearance,
+                        0);
+        mMinEms = typedArray.getInteger(R.styleable.AnimatedTemperatureView_android_minEms, 0);
+        mMinValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMinValue,
+                Float.NaN);
+        mMaxValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMaxValue,
+                Float.NaN);
+
+
+        mPaddingRect =
+                new Rect(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
+        setPadding(0, 0, 0, 0);
+
+        setClipChildren(false);
+        setClipToPadding(false);
+
+        // init Views
+        TextSwitcher textSwitcher = new TextSwitcher(context);
+        textSwitcher.setFactory(this::generateTextView);
+        ImageView background = new ImageView(context);
+        mBackgroundColor = new ColorDrawable(Color.TRANSPARENT);
+        background.setImageDrawable(mBackgroundColor);
+        background.setVisibility(View.GONE);
+
+        mBackgroundAnimator = new TemperatureBackgroundAnimator(this, background);
+
+
+        String format = typedArray.getString(R.styleable.AnimatedTemperatureView_hvacTempFormat);
+        format = (format == null) ? "%.1f\u00B0" : format;
+        CharSequence minText = typedArray.getString(
+                R.styleable.AnimatedTemperatureView_hvacMinText);
+        CharSequence maxText = typedArray.getString(
+                R.styleable.AnimatedTemperatureView_hvacMaxText);
+        mTextAnimator = new TemperatureTextAnimator(this, textSwitcher, format, mPivotOffset,
+                minText, maxText);
+
+        addView(background, ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        addView(textSwitcher, ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+
+        typedArray.recycle();
+
+        // register with controller
+        HvacController hvacController = Dependency.get(HvacController.class);
+        hvacController.addHvacTextView(this);
+    }
+
+    private TextView generateTextView() {
+        TextView textView = new TextView(getContext());
+        textView.setTextAppearance(mTextAppearanceRes);
+        textView.setAllCaps(true);
+        textView.setMinEms(mMinEms);
+        textView.setGravity(mGravity);
+        textView.setPadding(mPaddingRect.left, mPaddingRect.top, mPaddingRect.right,
+                mPaddingRect.bottom);
+        textView.getViewTreeObserver()
+                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        if (isHorizontal(mGravity)) {
+                            if (isLeft(mGravity, getLayoutDirection())) {
+                                textView.setPivotX(-mPivotOffset);
+                            } else {
+                                textView.setPivotX(textView.getWidth() + mPivotOffset);
+                            }
+                        }
+                        textView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        return false;
+                    }
+                });
+        textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+
+        return textView;
+    }
+
+    /**
+     * Formats the float for display
+     *
+     * @param temp - The current temp or NaN
+     */
+    @Override
+    public void setTemp(float temp) {
+        mTextAnimator.setTemp(temp);
+        if (Float.isNaN(temp)) {
+            mBackgroundAnimator.hideCircle();
+            return;
+        }
+        int color;
+        if (isMinValue(temp)) {
+            color = mColorStore.getMinColor();
+        } else if (isMaxValue(temp)) {
+            color = mColorStore.getMaxColor();
+        } else {
+            color = mColorStore.getColorForTemperature(temp);
+        }
+        if (mBackgroundAnimator.isOpen()) {
+            ObjectAnimator colorAnimator =
+                    ObjectAnimator.ofInt(mBackgroundColor, COLOR_PROPERTY, color);
+            colorAnimator.setEvaluator((fraction, startValue, endValue) -> mColorStore
+                    .lerpColor(fraction, (int) startValue, (int) endValue));
+            colorAnimator.start();
+        } else {
+            mBackgroundColor.setColor(color);
+        }
+
+        mBackgroundAnimator.animateOpen();
+    }
+
+    boolean isMinValue(float temp) {
+        return !Float.isNaN(mMinValue) && isApproxEqual(temp, mMinValue);
+    }
+
+    boolean isMaxValue(float temp) {
+        return !Float.isNaN(mMaxValue) && isApproxEqual(temp, mMaxValue);
+    }
+
+    private boolean isApproxEqual(float left, float right) {
+        return Math.abs(left - right) <= TEMPERATURE_EQUIVALENT_DELTA;
+    }
+
+    int getGravity() {
+        return mGravity;
+    }
+
+    int getPivotOffset() {
+        return mPivotOffset;
+    }
+
+    Rect getPaddingRect() {
+        return mPaddingRect;
+    }
+
+    /**
+     * @return propertiyId  Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (358614275)
+     */
+    @Override
+    public int getPropertyId() {
+        return mPropertyId;
+    }
+
+    /**
+     * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
+     */
+    @Override
+    public int getAreaId() {
+        return mAreaId;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mBackgroundAnimator.stopAnimations();
+    }
+
+}
+
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
new file mode 100644
index 0000000..0bc94b5
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isTop;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isVertical;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.IntDef;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.animation.AnticipateInterpolator;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls circular reveal animation of temperature background
+ */
+class TemperatureBackgroundAnimator {
+
+    private static final AnticipateInterpolator ANTICIPATE_INTERPOLATOR =
+            new AnticipateInterpolator();
+    private static final float MAX_OPACITY = .6f;
+
+    private final View mAnimatedView;
+
+    private int mPivotX;
+    private int mPivotY;
+    private int mGoneRadius;
+    private int mOvershootRadius;
+    private int mRestingRadius;
+    private int mBumpRadius;
+
+    @CircleState
+    private int mCircleState;
+
+    private Animator mCircularReveal;
+    private boolean mAnimationsReady;
+
+    @IntDef({CircleState.GONE, CircleState.ENTERING, CircleState.OVERSHOT, CircleState.RESTING,
+            CircleState.RESTED, CircleState.BUMPING, CircleState.BUMPED, CircleState.EXITING})
+    private @interface CircleState {
+        int GONE = 0;
+        int ENTERING = 1;
+        int OVERSHOT = 2;
+        int RESTING = 3;
+        int RESTED = 4;
+        int BUMPING = 5;
+        int BUMPED = 6;
+        int EXITING = 7;
+    }
+
+    TemperatureBackgroundAnimator(
+            AnimatedTemperatureView parent,
+            ImageView animatedView) {
+        mAnimatedView = animatedView;
+        mAnimatedView.setAlpha(0);
+
+        parent.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        setupAnimations(parent.getGravity(), parent.getPivotOffset(),
+                                parent.getPaddingRect(), parent.getWidth(), parent.getHeight()));
+    }
+
+    private void setupAnimations(int gravity, int pivotOffset, Rect paddingRect,
+            int width, int height) {
+        int padding;
+        if (isHorizontal(gravity)) {
+            mGoneRadius = pivotOffset;
+            if (isLeft(gravity, mAnimatedView.getLayoutDirection())) {
+                mPivotX = -pivotOffset;
+                padding = paddingRect.right;
+            } else {
+                mPivotX = width + pivotOffset;
+                padding = paddingRect.left;
+            }
+            mPivotY = height / 2;
+            mOvershootRadius = pivotOffset + width;
+        } else if (isVertical(gravity)) {
+            mGoneRadius = pivotOffset;
+            if (isTop(gravity)) {
+                mPivotY = -pivotOffset;
+                padding = paddingRect.bottom;
+            } else {
+                mPivotY = height + pivotOffset;
+                padding = paddingRect.top;
+            }
+            mPivotX = width / 2;
+            mOvershootRadius = pivotOffset + height;
+        } else {
+            mPivotX = width / 2;
+            mPivotY = height / 2;
+            mGoneRadius = 0;
+            if (width > height) {
+                mOvershootRadius = height;
+                padding = Math.max(paddingRect.top, paddingRect.bottom);
+            } else {
+                mOvershootRadius = width;
+                padding = Math.max(paddingRect.left, paddingRect.right);
+            }
+        }
+        mRestingRadius = mOvershootRadius - padding;
+        mBumpRadius = mOvershootRadius - padding / 3;
+        mAnimationsReady = true;
+    }
+
+    boolean isOpen() {
+        return mCircleState != CircleState.GONE;
+    }
+
+    void animateOpen() {
+        if (!mAnimationsReady || mCircleState == CircleState.ENTERING) {
+            return;
+        }
+
+        AnimatorSet set = new AnimatorSet();
+        List<Animator> animators = new ArrayList<>();
+        switch (mCircleState) {
+            case CircleState.ENTERING:
+                throw new AssertionError("Should not be able to reach this statement");
+            case CircleState.GONE: {
+                Animator startCircle = createEnterAnimator();
+                markState(startCircle, CircleState.ENTERING);
+                animators.add(startCircle);
+                Animator holdOvershoot = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius,
+                                mOvershootRadius);
+                holdOvershoot.setDuration(50);
+                markState(holdOvershoot, CircleState.OVERSHOT);
+                animators.add(holdOvershoot);
+                Animator rest = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius,
+                                mRestingRadius);
+                markState(rest, CircleState.RESTING);
+                animators.add(rest);
+                Animator holdRest = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius,
+                                mRestingRadius);
+                markState(holdRest, CircleState.RESTED);
+                holdRest.setDuration(1000);
+                animators.add(holdRest);
+                Animator exit = createExitAnimator(mRestingRadius);
+                markState(exit, CircleState.EXITING);
+                animators.add(exit);
+            }
+            break;
+            case CircleState.RESTED:
+            case CircleState.RESTING:
+            case CircleState.EXITING:
+            case CircleState.OVERSHOT:
+                int startRadius =
+                        mCircleState == CircleState.OVERSHOT ? mOvershootRadius : mRestingRadius;
+                Animator bump = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius,
+                                mBumpRadius);
+                bump.setDuration(50);
+                markState(bump, CircleState.BUMPING);
+                animators.add(bump);
+                // fallthrough intentional
+            case CircleState.BUMPED:
+            case CircleState.BUMPING:
+                Animator holdBump = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius,
+                                mBumpRadius);
+                holdBump.setDuration(100);
+                markState(holdBump, CircleState.BUMPED);
+                animators.add(holdBump);
+                Animator rest = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius,
+                                mRestingRadius);
+                markState(rest, CircleState.RESTING);
+                animators.add(rest);
+                Animator holdRest = ViewAnimationUtils
+                        .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius,
+                                mRestingRadius);
+                holdRest.setDuration(1000);
+                markState(holdRest, CircleState.RESTED);
+                animators.add(holdRest);
+                Animator exit = createExitAnimator(mRestingRadius);
+                markState(exit, CircleState.EXITING);
+                animators.add(exit);
+                break;
+        }
+        set.playSequentially(animators);
+        set.addListener(new AnimatorListenerAdapter() {
+            private boolean mCanceled = false;
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (mCircularReveal != null) {
+                    mCircularReveal.cancel();
+                }
+                mCircularReveal = animation;
+                mAnimatedView.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCanceled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCanceled) {
+                    return;
+                }
+                mCircularReveal = null;
+                mCircleState = CircleState.GONE;
+                mAnimatedView.setVisibility(View.GONE);
+            }
+        });
+
+        set.start();
+    }
+
+    private Animator createEnterAnimator() {
+        AnimatorSet animatorSet = new AnimatorSet();
+        Animator circularReveal = ViewAnimationUtils
+                .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mGoneRadius,
+                        mOvershootRadius);
+        Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, MAX_OPACITY);
+        animatorSet.playTogether(circularReveal, fade);
+        return animatorSet;
+    }
+
+    private Animator createExitAnimator(int startRadius) {
+        AnimatorSet animatorSet = new AnimatorSet();
+        Animator circularHide = ViewAnimationUtils
+                .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius,
+                        (mGoneRadius + startRadius) / 2);
+        circularHide.setInterpolator(ANTICIPATE_INTERPOLATOR);
+        Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, 0);
+        fade.setStartDelay(50);
+        animatorSet.playTogether(circularHide, fade);
+        return animatorSet;
+    }
+
+    void hideCircle() {
+        if (!mAnimationsReady || mCircleState == CircleState.GONE
+                || mCircleState == CircleState.EXITING) {
+            return;
+        }
+
+        int startRadius;
+        switch (mCircleState) {
+            // Unreachable, but here to exhaust switch cases
+            //noinspection ConstantConditions
+            case CircleState.EXITING:
+                //noinspection ConstantConditions
+            case CircleState.GONE:
+                throw new AssertionError("Should not be able to reach this statement");
+            case CircleState.BUMPED:
+            case CircleState.BUMPING:
+                startRadius = mBumpRadius;
+                break;
+            case CircleState.OVERSHOT:
+                startRadius = mOvershootRadius;
+                break;
+            case CircleState.ENTERING:
+            case CircleState.RESTED:
+            case CircleState.RESTING:
+                startRadius = mRestingRadius;
+                break;
+            default:
+                throw new IllegalStateException("Unknown CircleState: " + mCircleState);
+        }
+
+        Animator hideAnimation = createExitAnimator(startRadius);
+        if (startRadius == mRestingRadius) {
+            hideAnimation.setInterpolator(ANTICIPATE_INTERPOLATOR);
+        }
+        hideAnimation.addListener(new AnimatorListenerAdapter() {
+            private boolean mCanceled = false;
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mCircleState = CircleState.EXITING;
+                if (mCircularReveal != null) {
+                    mCircularReveal.cancel();
+                }
+                mCircularReveal = animation;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCanceled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCanceled) {
+                    return;
+                }
+                mCircularReveal = null;
+                mCircleState = CircleState.GONE;
+                mAnimatedView.setVisibility(View.GONE);
+            }
+        });
+        hideAnimation.start();
+    }
+
+    void stopAnimations() {
+        if (mCircularReveal != null) {
+            mCircularReveal.end();
+        }
+    }
+
+    private void markState(Animator animator, @CircleState int startState) {
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mCircleState = startState;
+            }
+        });
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
new file mode 100644
index 0000000..a40ffaf
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import android.graphics.Color;
+
+/**
+ * Contains the logic for mapping colors to temperatures
+ */
+class TemperatureColorStore {
+
+    private static class TemperatureColorValue {
+        final float mTemperature;
+        final int mColor;
+
+        private TemperatureColorValue(float temperature, int color) {
+            this.mTemperature = temperature;
+            this.mColor = color;
+        }
+
+        float getTemperature() {
+            return mTemperature;
+        }
+
+        int getColor() {
+            return mColor;
+        }
+    }
+
+    private static TemperatureColorValue tempToColor(float temperature, int color) {
+        return new TemperatureColorValue(temperature, color);
+    }
+
+    private static final int COLOR_COLDEST = 0xFF406DFF;
+    private static final int COLOR_COLD = 0xFF4094FF;
+    private static final int COLOR_NEUTRAL = 0xFFF4F4F4;
+    private static final int COLOR_WARM = 0xFFFF550F;
+    private static final int COLOR_WARMEST = 0xFFFF0000;
+    // must be sorted by temperature
+    private static final TemperatureColorValue[] sTemperatureColorValues =
+            {
+                    // Celsius
+                    tempToColor(19, COLOR_COLDEST),
+                    tempToColor(21, COLOR_COLD),
+                    tempToColor(23, COLOR_NEUTRAL),
+                    tempToColor(25, COLOR_WARM),
+                    tempToColor(27, COLOR_WARMEST),
+
+                    // Switch over
+                    tempToColor(45, COLOR_WARMEST),
+                    tempToColor(45.00001f, COLOR_COLDEST),
+
+                    // Farenheight
+                    tempToColor(66, COLOR_COLDEST),
+                    tempToColor(70, COLOR_COLD),
+                    tempToColor(74, COLOR_NEUTRAL),
+                    tempToColor(76, COLOR_WARM),
+                    tempToColor(80, COLOR_WARMEST)
+            };
+
+    private static final int COLOR_UNSET = Color.BLACK;
+
+    private final float[] mTempHsv1 = new float[3];
+    private final float[] mTempHsv2 = new float[3];
+    private final float[] mTempHsv3 = new float[3];
+
+    int getMinColor() {
+        return COLOR_COLDEST;
+    }
+
+    int getMaxColor() {
+        return COLOR_WARMEST;
+    }
+
+    int getColorForTemperature(float temperature) {
+        if (Float.isNaN(temperature)) {
+            return COLOR_UNSET;
+        }
+        TemperatureColorValue bottomValue = sTemperatureColorValues[0];
+        if (temperature <= bottomValue.getTemperature()) {
+            return bottomValue.getColor();
+        }
+        TemperatureColorValue topValue =
+                sTemperatureColorValues[sTemperatureColorValues.length - 1];
+        if (temperature >= topValue.getTemperature()) {
+            return topValue.getColor();
+        }
+
+        int index = binarySearch(temperature);
+        if (index >= 0) {
+            return sTemperatureColorValues[index].getColor();
+        }
+
+        index = -index - 1; // move to the insertion point
+
+        TemperatureColorValue startValue = sTemperatureColorValues[index - 1];
+        TemperatureColorValue endValue = sTemperatureColorValues[index];
+        float fraction = (temperature - startValue.getTemperature()) / (endValue.getTemperature()
+                - startValue.getTemperature());
+        return lerpColor(fraction, startValue.getColor(), endValue.getColor());
+    }
+
+    int lerpColor(float fraction, int startColor, int endColor) {
+        float[] startHsv = mTempHsv1;
+        Color.colorToHSV(startColor, startHsv);
+        float[] endHsv = mTempHsv2;
+        Color.colorToHSV(endColor, endHsv);
+
+        // If a target color is white/gray, it should use the same hue as the other target
+        if (startHsv[1] == 0) {
+            startHsv[0] = endHsv[0];
+        }
+        if (endHsv[1] == 0) {
+            endHsv[0] = startHsv[0];
+        }
+
+        float[] outColor = mTempHsv3;
+        outColor[0] = hueLerp(fraction, startHsv[0], endHsv[0]);
+        outColor[1] = lerp(fraction, startHsv[1], endHsv[1]);
+        outColor[2] = lerp(fraction, startHsv[2], endHsv[2]);
+
+        return Color.HSVToColor(outColor);
+    }
+
+    private float hueLerp(float fraction, float start, float end) {
+        // If in flat part of curve, no interpolation necessary
+        if (start == end) {
+            return start;
+        }
+
+        // If the hues are more than 180 degrees apart, go the other way around the color wheel
+        // by moving the smaller value above 360
+        if (Math.abs(start - end) > 180f) {
+            if (start < end) {
+                start += 360f;
+            } else {
+                end += 360f;
+            }
+        }
+        // Lerp and ensure the final output is within [0, 360)
+        return lerp(fraction, start, end) % 360f;
+
+    }
+
+    private float lerp(float fraction, float start, float end) {
+        // If in flat part of curve, no interpolation necessary
+        if (start == end) {
+            return start;
+        }
+
+        // If outside bounds, use boundary value
+        if (fraction >= 1) {
+            return end;
+        }
+        if (fraction <= 0) {
+            return start;
+        }
+
+        return (end - start) * fraction + start;
+    }
+
+    private int binarySearch(float temperature) {
+        int low = 0;
+        int high = sTemperatureColorValues.length;
+
+        while (low <= high) {
+            int mid = (low + high) >>> 1;
+            float midVal = sTemperatureColorValues[mid].getTemperature();
+
+            if (midVal < temperature) {
+                low = mid + 1;  // Neither val is NaN, thisVal is smaller
+            } else if (midVal > temperature) {
+                high = mid - 1; // Neither val is NaN, thisVal is larger
+            } else {
+                int midBits = Float.floatToIntBits(midVal);
+                int keyBits = Float.floatToIntBits(temperature);
+                if (midBits == keyBits) {    // Values are equal
+                    return mid;             // Key found
+                } else if (midBits < keyBits) { // (-0.0, 0.0) or (!NaN, NaN)
+                    low = mid + 1;
+                } else {                        /* (0.0, -0.0) or (NaN, !NaN)*/
+                    high = mid - 1;
+                }
+            }
+        }
+        return -(low + 1);  // key not found.
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
new file mode 100644
index 0000000..8ee5ef6
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+
+import android.annotation.NonNull;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.RotateAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.TextSwitcher;
+
+/**
+ * Controls animating TemperatureView's text
+ */
+class TemperatureTextAnimator {
+
+    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
+            new DecelerateInterpolator();
+    private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR =
+            new AccelerateDecelerateInterpolator();
+
+    private static final int ROTATION_DEGREES = 15;
+    private static final int DURATION_MILLIS = 200;
+
+    private AnimatedTemperatureView mParent;
+    private final TextSwitcher mTextSwitcher;
+    private final String mTempFormat;
+    private final int mPivotOffset;
+    private final CharSequence mMinText;
+    private final CharSequence mMaxText;
+
+    private Animation mTextInAnimationUp;
+    private Animation mTextOutAnimationUp;
+    private Animation mTextInAnimationDown;
+    private Animation mTextOutAnimationDown;
+    private Animation mTextFadeInAnimation;
+    private Animation mTextFadeOutAnimation;
+
+    private float mLastTemp = Float.NaN;
+
+    TemperatureTextAnimator(AnimatedTemperatureView parent, TextSwitcher textSwitcher,
+            String tempFormat, int pivotOffset,
+            CharSequence minText, CharSequence maxText) {
+        mParent = parent;
+        mTextSwitcher = textSwitcher;
+        mTempFormat = tempFormat;
+        mPivotOffset = pivotOffset;
+        mMinText = minText;
+        mMaxText = maxText;
+
+        mParent.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        setupAnimations(mParent.getGravity()));
+    }
+
+    void setTemp(float temp) {
+        if (Float.isNaN(temp)) {
+            mTextSwitcher.setInAnimation(mTextFadeInAnimation);
+            mTextSwitcher.setOutAnimation(mTextFadeOutAnimation);
+            mTextSwitcher.setText("--");
+            mLastTemp = temp;
+            return;
+        }
+        boolean isMinValue = mParent.isMinValue(temp);
+        boolean isMaxValue = mParent.isMaxValue(temp);
+        if (Float.isNaN(mLastTemp)) {
+            mTextSwitcher.setInAnimation(mTextFadeInAnimation);
+            mTextSwitcher.setOutAnimation(mTextFadeOutAnimation);
+        } else if (!isMinValue && (isMaxValue || temp > mLastTemp)) {
+            mTextSwitcher.setInAnimation(mTextInAnimationUp);
+            mTextSwitcher.setOutAnimation(mTextOutAnimationUp);
+        } else {
+            mTextSwitcher.setInAnimation(mTextInAnimationDown);
+            mTextSwitcher.setOutAnimation(mTextOutAnimationDown);
+        }
+        CharSequence text;
+        if (isMinValue) {
+            text = mMinText;
+        } else if (isMaxValue) {
+            text = mMaxText;
+        } else {
+            text = String.format(mTempFormat, temp);
+        }
+        mTextSwitcher.setText(text);
+        mLastTemp = temp;
+    }
+
+    private void setupAnimations(int gravity) {
+        mTextFadeInAnimation = createFadeAnimation(true);
+        mTextFadeOutAnimation = createFadeAnimation(false);
+        if (!isHorizontal(gravity)) {
+            mTextInAnimationUp = createTranslateFadeAnimation(true, true);
+            mTextOutAnimationUp = createTranslateFadeAnimation(false, true);
+            mTextInAnimationDown = createTranslateFadeAnimation(true, false);
+            mTextOutAnimationDown = createTranslateFadeAnimation(false, false);
+        } else {
+            boolean isLeft = isLeft(gravity, mTextSwitcher.getLayoutDirection());
+            mTextInAnimationUp = createRotateFadeAnimation(true, isLeft, true);
+            mTextOutAnimationUp = createRotateFadeAnimation(false, isLeft, true);
+            mTextInAnimationDown = createRotateFadeAnimation(true, isLeft, false);
+            mTextOutAnimationDown = createRotateFadeAnimation(false, isLeft, false);
+        }
+    }
+
+    @NonNull
+    private Animation createFadeAnimation(boolean in) {
+        AnimationSet set = new AnimationSet(true);
+        AlphaAnimation alphaAnimation = new AlphaAnimation(in ? 0 : 1, in ? 1 : 0);
+        alphaAnimation.setDuration(DURATION_MILLIS);
+        set.addAnimation(new RotateAnimation(0, 0)); // Undo any previous rotation
+        set.addAnimation(alphaAnimation);
+        return set;
+    }
+
+    @NonNull
+    private Animation createTranslateFadeAnimation(boolean in, boolean up) {
+        AnimationSet set = new AnimationSet(true);
+        set.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);
+        set.setDuration(DURATION_MILLIS);
+        int fromYDelta = in ? (up ? 1 : -1) : 0;
+        int toYDelta = in ? 0 : (up ? -1 : 1);
+        set.addAnimation(
+                new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
+                        Animation.RELATIVE_TO_SELF, fromYDelta, Animation.RELATIVE_TO_SELF,
+                        toYDelta));
+        set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0));
+        return set;
+    }
+
+    @NonNull
+    private Animation createRotateFadeAnimation(boolean in, boolean isLeft, boolean up) {
+        AnimationSet set = new AnimationSet(true);
+        set.setInterpolator(DECELERATE_INTERPOLATOR);
+        set.setDuration(DURATION_MILLIS);
+
+        float degrees = isLeft == up ? -ROTATION_DEGREES : ROTATION_DEGREES;
+        int pivotX = isLeft ? -mPivotOffset : mParent.getWidth() + mPivotOffset;
+        set.addAnimation(
+                new RotateAnimation(in ? -degrees : 0f, in ? 0f : degrees, Animation.ABSOLUTE,
+                        pivotX, Animation.ABSOLUTE, 0f));
+        set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0));
+        return set;
+    }
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index fdd6a9c..a539b1f 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -19,7 +19,9 @@
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 
+import android.annotation.NonNull;
 import android.app.INotificationManager;
+import android.app.Notification;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -80,6 +82,7 @@
 
     private float mDismissToViewRatioLimit;
     private int mStreakLimit;
+    private SmartActionsHelper mSmartActionsHelper;
 
     // key : impressions tracker
     // TODO: prune deleted channels and apps
@@ -99,6 +102,7 @@
         // Contexts are correctly hooked up by the creation step, which is required for the observer
         // to be hooked up/initialized.
         new SettingsObserver(mHandler);
+        mSmartActionsHelper = new SmartActionsHelper();
     }
 
     private void loadFile() {
@@ -187,7 +191,26 @@
     @Override
     public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
         if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
-        return null;
+        ArrayList<Notification.Action> actions =
+                mSmartActionsHelper.suggestActions(this, sbn);
+        if (actions.isEmpty()) {
+            return null;
+        }
+        return createEnqueuedNotificationAdjustment(sbn, actions);
+    }
+
+    /** A convenience helper for creating an adjustment for an SBN. */
+    private Adjustment createEnqueuedNotificationAdjustment(
+            @NonNull StatusBarNotification statusBarNotification,
+            @NonNull ArrayList<Notification.Action> smartActions) {
+        Bundle signals = new Bundle();
+        signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
+        return new Adjustment(
+                statusBarNotification.getPackageName(),
+                statusBarNotification.getKey(),
+                signals,
+                "smart action" /* explanation */,
+                statusBarNotification.getUserId());
     }
 
     @Override
@@ -378,4 +401,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
new file mode 100644
index 0000000..1754461
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (C) 2018 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.ext.services.notification;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class SmartActionsHelper {
+    private static final ArrayList<Notification.Action> EMPTY_LIST = new ArrayList<>();
+
+    // If a notification has any of these flags set, it's inelgibile for actions being added.
+    private static final int FLAG_MASK_INELGIBILE_FOR_ACTIONS =
+            Notification.FLAG_ONGOING_EVENT
+                    | Notification.FLAG_FOREGROUND_SERVICE
+                    | Notification.FLAG_GROUP_SUMMARY
+                    | Notification.FLAG_NO_CLEAR;
+    private static final int MAX_ACTION_EXTRACTION_TEXT_LENGTH = 400;
+    private static final int MAX_ACTIONS_PER_LINK = 1;
+    private static final int MAX_SMART_ACTIONS = Notification.MAX_ACTION_BUTTONS;
+
+    SmartActionsHelper() {}
+
+    /**
+     * Adds action adjustments based on the notification contents.
+     *
+     * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions
+     * from notification text / message, we can replace most of the code here by consuming that API.
+     */
+    @NonNull
+    ArrayList<Notification.Action> suggestActions(
+            @Nullable Context context, @NonNull StatusBarNotification sbn) {
+        if (!isEligibleForActionAdjustment(sbn)) {
+            return EMPTY_LIST;
+        }
+        if (context == null) {
+            return EMPTY_LIST;
+        }
+        TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class);
+        if (tcm == null) {
+            return EMPTY_LIST;
+        }
+        Notification.Action[] actions = sbn.getNotification().actions;
+        int numOfExistingActions = actions == null ? 0: actions.length;
+        int maxSmartActions = MAX_SMART_ACTIONS - numOfExistingActions;
+        return suggestActionsFromText(
+                tcm,
+                getMostSalientActionText(sbn.getNotification()), maxSmartActions);
+    }
+
+    /**
+     * Returns whether a notification is eligible for action adjustments.
+     *
+     * <p>We exclude system notifications, those that get refreshed frequently, or ones that relate
+     * to fundamental phone functionality where any error would result in a very negative user
+     * experience.
+     */
+    private boolean isEligibleForActionAdjustment(@NonNull StatusBarNotification sbn) {
+        Notification notification = sbn.getNotification();
+        String pkg = sbn.getPackageName();
+        if (notification.actions != null
+                && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) {
+            return false;
+        }
+        if (0 != (notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS)) {
+            return false;
+        }
+        if (TextUtils.isEmpty(pkg) || pkg.equals("android")) {
+            return false;
+        }
+        // For now, we are only interested in messages.
+        return Notification.CATEGORY_MESSAGE.equals(notification.category)
+                || Notification.MessagingStyle.class.equals(notification.getNotificationStyle());
+    }
+
+    /** Returns the text most salient for action extraction in a notification. */
+    @Nullable
+    private CharSequence getMostSalientActionText(@NonNull Notification notification) {
+        /* If it's messaging style, use the most recent message. */
+        Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+        if (messages != null && messages.length != 0) {
+            Bundle lastMessage = (Bundle) messages[messages.length - 1];
+            CharSequence lastMessageText =
+                    lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT);
+            if (!TextUtils.isEmpty(lastMessageText)) {
+                return lastMessageText;
+            }
+        }
+
+        // Fall back to using the normal text.
+        return notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+    }
+
+    /** Returns a list of actions to act on entities in a given piece of text. */
+    @NonNull
+    private ArrayList<Notification.Action> suggestActionsFromText(
+            @NonNull TextClassificationManager tcm, @Nullable CharSequence text,
+            int maxSmartActions) {
+        if (TextUtils.isEmpty(text)) {
+            return EMPTY_LIST;
+        }
+        TextClassifier textClassifier = tcm.getTextClassifier();
+
+        // We want to process only text visible to the user to avoid confusing suggestions, so we
+        // truncate the text to a reasonable length. This is particularly important for e.g.
+        // email apps that sometimes include the text for the entire thread.
+        text = text.subSequence(0, Math.min(text.length(), MAX_ACTION_EXTRACTION_TEXT_LENGTH));
+
+        // Extract all entities.
+        TextLinks.Request textLinksRequest = new TextLinks.Request.Builder(text)
+                .setEntityConfig(
+                        TextClassifier.EntityConfig.createWithHints(
+                                Collections.singletonList(
+                                        TextClassifier.HINT_TEXT_IS_NOT_EDITABLE)))
+                .build();
+        TextLinks links = textClassifier.generateLinks(textLinksRequest);
+        ArrayMap<String, Integer> entityTypeFrequency = getEntityTypeFrequency(links);
+
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        for (TextLinks.TextLink link : links.getLinks()) {
+            // Ignore any entity type for which we have too many entities. This is to handle the
+            // case where a notification contains e.g. a list of phone numbers. In such cases, the
+            // user likely wants to act on the whole list rather than an individual entity.
+            if (link.getEntityCount() == 0
+                    || entityTypeFrequency.get(link.getEntity(0)) != 1) {
+                continue;
+            }
+
+            // Generate the actions, and add the most prominent ones to the action bar.
+            TextClassification classification =
+                    textClassifier.classifyText(
+                            new TextClassification.Request.Builder(
+                                    text, link.getStart(), link.getEnd()).build());
+            int numOfActions = Math.min(
+                    MAX_ACTIONS_PER_LINK, classification.getActions().size());
+            for (int i = 0; i < numOfActions; ++i) {
+                RemoteAction action = classification.getActions().get(i);
+                actions.add(
+                        new Notification.Action.Builder(
+                                action.getIcon(),
+                                action.getTitle(),
+                                action.getActionIntent())
+                                .build());
+                // We have enough smart actions.
+                if (actions.size() >= maxSmartActions) {
+                    return actions;
+                }
+            }
+        }
+        return actions;
+    }
+
+    /**
+     * Given the links extracted from a piece of text, returns the frequency of each entity
+     * type.
+     */
+    @NonNull
+    private ArrayMap<String, Integer> getEntityTypeFrequency(@NonNull TextLinks links) {
+        ArrayMap<String, Integer> entityTypeCount = new ArrayMap<>();
+        for (TextLinks.TextLink link : links.getLinks()) {
+            if (link.getEntityCount() == 0) {
+                continue;
+            }
+            String entityType = link.getEntity(0);
+            if (entityTypeCount.containsKey(entityType)) {
+                entityTypeCount.put(entityType, entityTypeCount.get(entityType) + 1);
+            } else {
+                entityTypeCount.put(entityType, 1);
+            }
+        }
+        return entityTypeCount;
+    }
+}
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
deleted file mode 100644
index b6a5eb5..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
deleted file mode 100644
index 4e36bd2..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png
deleted file mode 100644
index bb9d855..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png
deleted file mode 100644
index 2757db0..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
deleted file mode 100644
index 428a946..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
deleted file mode 100644
index 3220eea..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
deleted file mode 100644
index fbbd094..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
deleted file mode 100644
index 5530f52..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png
deleted file mode 100644
index bd611e8..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png
deleted file mode 100644
index c1b380a..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
deleted file mode 100644
index 6161c20..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
deleted file mode 100644
index 3a89805..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png
deleted file mode 100644
index a7fdc0d..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png
deleted file mode 100644
index fedc00e..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
deleted file mode 100644
index 52a52d9..0000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
deleted file mode 100644
index 15e6abd..0000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
deleted file mode 100644
index 46811a1..0000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
deleted file mode 100644
index 141f28b..0000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/ic_clear.xml b/packages/PrintSpooler/res/drawable/ic_clear.xml
new file mode 100644
index 0000000..076e8ef
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_clear.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
+        android:fillColor="?android:colorForeground"/>
+</vector>
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_less.xml b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
index 6f1ece1..c3e87dc 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_less.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
@@ -1,43 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+  Copyright (C) 2018 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
+  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,
-     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.
--->
+  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.
+  -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:autoMirrored="true">
-
-    <item
-        android:state_checked="true">
-        <bitmap
-            android:src="@drawable/ic_expand_less"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item
-        android:state_pressed="true">
-        <bitmap
-            android:src="@drawable/ic_expand_less"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item>
-        <bitmap
-            android:src="@drawable/ic_expand_less"
-            android:tint="?android:attr/colorControlNormal">
-        </bitmap>
-    </item>
-
-</selector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"
+        android:fillColor="?android:colorForeground" />
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_more.xml b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
index 8d71452..3895144 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_more.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
@@ -1,43 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+  Copyright (C) 2018 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
+  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,
-     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.
--->
+  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.
+  -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:autoMirrored="true">
-
-    <item
-        android:state_checked="true">
-        <bitmap
-            android:src="@drawable/ic_expand_more"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item
-        android:state_pressed="true">
-        <bitmap
-            android:src="@drawable/ic_expand_more"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item>
-        <bitmap
-            android:src="@drawable/ic_expand_more"
-            android:tint="?android:attr/colorControlNormal">
-        </bitmap>
-    </item>
-
-</selector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6l-6,-6L7.41,8.59z"
+        android:fillColor="?android:colorForeground" />
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/preview_page.xml b/packages/PrintSpooler/res/layout/preview_page.xml
index aafdd8f..8db347e 100644
--- a/packages/PrintSpooler/res/layout/preview_page.xml
+++ b/packages/PrintSpooler/res/layout/preview_page.xml
@@ -44,7 +44,7 @@
             android:layout_height="wrap_content"
             android:layout_centerInParent="true"
             android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorPrimary">
+            android:textColor="@android:color/white">
         </TextView>
 
         <ImageView
diff --git a/packages/PrintSpooler/res/layout/preview_page_error.xml b/packages/PrintSpooler/res/layout/preview_page_error.xml
index 4e9fb77..99ab99d 100644
--- a/packages/PrintSpooler/res/layout/preview_page_error.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_error.xml
@@ -21,11 +21,14 @@
         android:gravity="center">
 
     <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="12dip"
-            android:src="@drawable/print_warning"
-            android:contentDescription="@null" />
+        android:layout_width="120dp"
+        android:layout_height="110dp"
+        android:layout_marginBottom="12dip"
+        android:src="@*android:drawable/ic_print_error"
+        android:scaleType="fitEnd"
+        android:alpha="0.1"
+        android:tint="@android:color/black"
+        android:importantForAccessibility="no" />
 
     <TextView
             android:layout_width="wrap_content"
diff --git a/packages/PrintSpooler/res/layout/preview_page_loading.xml b/packages/PrintSpooler/res/layout/preview_page_loading.xml
index 1af3a17..918edd9 100644
--- a/packages/PrintSpooler/res/layout/preview_page_loading.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_loading.xml
@@ -19,14 +19,13 @@
     android:layout_height="fill_parent">
 
     <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="36dip"
+        android:layout_width="120dp"
+        android:layout_height="110dp"
         android:layout_gravity="center"
-        android:src="@drawable/ic_grayedout_printer"
-        android:contentDescription="@null"
-        android:scaleType="centerInside"
-        android:adjustViewBounds="true">
-    </ImageView>
+        android:src="@*android:drawable/ic_print"
+        android:scaleType="fitCenter"
+        android:alpha="0.1"
+        android:tint="@android:color/black"
+        android:importantForAccessibility="no" />
 
 </FrameLayout>
diff --git a/packages/PrintSpooler/res/layout/preview_page_selected.xml b/packages/PrintSpooler/res/layout/preview_page_selected.xml
index 77f4727..6727142 100644
--- a/packages/PrintSpooler/res/layout/preview_page_selected.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_selected.xml
@@ -42,7 +42,7 @@
             android:layout_height="wrap_content"
             android:layout_centerInParent="true"
             android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorPrimary">
+            android:textColor="@android:color/white">
         </TextView>
 
         <ImageView
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 774f320..212f398 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -29,7 +29,7 @@
         android:paddingStart="8dip"
         android:layout_marginEnd="16dp"
         android:elevation="@dimen/preview_controls_elevation"
-        android:background="?android:attr/colorPrimary">
+        style="?android:actionBarStyle">
 
         <com.android.printspooler.widget.ClickInterceptSpinner
             android:id="@+id/destination_spinner"
@@ -55,7 +55,7 @@
         android:paddingBottom="8dip"
         android:orientation="horizontal"
         android:elevation="@dimen/preview_controls_elevation"
-        android:background="?android:attr/colorPrimary">
+        style="?android:actionBarStyle">
 
         <TextView
             android:layout_width="wrap_content"
@@ -121,7 +121,6 @@
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:animateLayoutChanges="true"
-        android:background="@color/print_preview_background_color"
         android:gravity="center">
 
         <!-- Error message added here -->
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
index 69d4f91..3aafc99 100644
--- a/packages/PrintSpooler/res/layout/print_activity_controls.xml
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -22,7 +22,7 @@
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:elevation="@dimen/preview_controls_elevation"
-    android:background="?android:attr/colorPrimary">
+    style="?android:actionBarStyle">
 
     <LinearLayout
         android:id="@+id/draggable_content"
diff --git a/packages/PrintSpooler/res/layout/print_error_fragment.xml b/packages/PrintSpooler/res/layout/print_error_fragment.xml
index 3ea2abd..9d9dd01 100644
--- a/packages/PrintSpooler/res/layout/print_error_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_error_fragment.xml
@@ -16,35 +16,39 @@
  -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="16dip"
+    android:layout_marginEnd="16dip"
     android:gravity="center"
     android:orientation="vertical">
 
     <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="12dip"
-        android:src="@drawable/ic_grayedout_printer"
-        android:contentDescription="@null">
-    </ImageView>
+        android:layout_width="120dp"
+        android:layout_height="110dp"
+        android:layout_marginBottom="12dp"
+        android:src="@*android:drawable/ic_print_error"
+        android:scaleType="fitEnd"
+        android:alpha="0.1"
+        android:tint="?android:colorForeground"
+        android:importantForAccessibility="no" />
 
     <TextView
         android:id="@+id/message"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="16dip"
-        android:layout_marginEnd="16dip"
-        android:gravity="center_horizontal"
-        android:text="@string/print_error_default_message"
-        android:textAppearance="?android:attr/textAppearanceLargeInverse">
-    </TextView>
+        android:layout_marginBottom="16dp"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="?android:attr/textColorSecondary"
+        android:text="@string/print_error_default_message" />
 
     <Button
         android:id="@+id/action_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="@string/print_error_retry">
-    </Button>
+        style="?android:attr/borderlessButtonStyle"
+        android:textColor="?android:attr/textColorSecondary"
+        android:text="@string/print_add_printer" />
 
 </LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_progress_fragment.xml b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
index 3b010f8..89071605 100644
--- a/packages/PrintSpooler/res/layout/print_progress_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
@@ -15,34 +15,39 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
     android:gravity="center"
     android:orientation="vertical">
 
     <ImageView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="12dip"
-        android:src="@drawable/ic_grayedout_printer"
-        android:contentDescription="@null">
-    </ImageView>
-
-    <ProgressBar
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:indeterminate="true"
-        style="?android:attr/progressBarStyleHorizontal">
-    </ProgressBar>
+        android:layout_width="120dp"
+        android:layout_height="110dp"
+        android:layout_marginBottom="12dp"
+        android:src="@*android:drawable/ic_print"
+        android:scaleType="fitEnd"
+        android:alpha="0.1"
+        android:tint="?android:colorForeground"
+        android:importantForAccessibility="no" />
 
     <TextView
         android:id="@+id/message"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceLargeInverse"
-        android:text="@string/print_preparing_preview">
-    </TextView>
+        android:layout_marginBottom="16dp"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="?android:attr/textColorSecondary"
+        android:text="@string/print_preparing_preview" />
+
+    <ProgressBar
+        android:layout_width="300dp"
+        android:layout_height="wrap_content"
+        android:indeterminate="true"
+        android:importantForAccessibility="no"
+        style="?android:attr/progressBarStyleHorizontal" />
 
 </LinearLayout>
 
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
index 91beff6..681924b 100644
--- a/packages/PrintSpooler/res/layout/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -31,45 +31,48 @@
         android:visibility="gone">
 
         <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
             android:gravity="center"
             android:orientation="vertical">
 
             <ImageView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="12dip"
-                android:src="@*android:drawable/ic_grayedout_printer"
-                android:importantForAccessibility="no">
-            </ImageView>
+                android:layout_width="120dp"
+                android:layout_height="110dp"
+                android:layout_marginBottom="12dp"
+                android:src="@*android:drawable/ic_print"
+                android:scaleType="fitEnd"
+                android:alpha="0.1"
+                android:tint="?android:colorForeground"
+                android:importantForAccessibility="no" />
 
             <TextView
                 android:id="@+id/title"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                android:gravity="center"
                 android:textAppearance="?android:attr/textAppearanceLarge"
                 android:textColor="?android:attr/textColorSecondary"
-                android:text="@string/print_searching_for_printers">
-            </TextView>
+                android:text="@string/print_searching_for_printers" />
 
             <ProgressBar
                 android:id="@+id/progress_bar"
-                android:layout_width="fill_parent"
+                android:layout_width="300dp"
                 android:layout_height="wrap_content"
                 android:indeterminate="true"
-                style="?android:attr/progressBarStyleHorizontal">
-            </ProgressBar>
+                android:importantForAccessibility="no"
+                style="?android:attr/progressBarStyleHorizontal" />
 
             <Button
-                    android:id="@+id/button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    style="?android:attr/buttonBarButtonStyle"
-                    android:textAppearance="?android:attr/textAppearanceSmall"
-                    android:text="@string/print_add_printer"
-                    android:textAllCaps="true" />
+                android:id="@+id/button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="?android:attr/borderlessButtonStyle"
+                android:textColor="?android:attr/textColorSecondary"
+                android:text="@string/print_add_printer" />
 
         </LinearLayout>
 
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index 68bc6f2..cb9e886 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -18,8 +18,6 @@
 
     <color name="print_preview_scrim_color">#99000000</color>
 
-    <color name="print_preview_background_color">#F2F1F2</color>
-
     <color name="unselected_page_background_color">#C0C0C0</color>
 
     <color name="material_grey_500">#ffa3a3a3</color>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index a968ffa..844e9c9 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -21,17 +21,14 @@
     </style>
 
     <style name="Theme.SelectPrinterActivity"
-           parent="android:style/Theme.DeviceDefault.Light.DarkActionBar">
+           parent="android:style/Theme.DeviceDefault.Light">
         <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
     </style>
 
-    <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault">
+    <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
-        <item name="android:backgroundDimEnabled">false</item>
     </style>
 
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 9d737e0..abdfad5 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -166,7 +166,7 @@
      */
     private Action createCancelAction(PrintJobInfo printJob) {
         return new Action.Builder(
-                Icon.createWithResource(mContext, R.drawable.stat_notify_cancelling),
+                Icon.createWithResource(mContext, R.drawable.ic_clear),
                 mContext.getString(R.string.cancel), createCancelIntent(printJob)).build();
     }
 
@@ -225,7 +225,7 @@
 
     private void createFailedNotification(PrintJobInfo printJob) {
         Action.Builder restartActionBuilder = new Action.Builder(
-                Icon.createWithResource(mContext, R.drawable.ic_restart),
+                Icon.createWithResource(mContext, com.android.internal.R.drawable.ic_restart),
                 mContext.getString(R.string.restart), createRestartIntent(printJob.getId()));
 
         createNotification(printJob, createCancelAction(printJob), restartActionBuilder.build());
@@ -317,7 +317,7 @@
                 if (!printJob.isCancelling()) {
                     return com.android.internal.R.drawable.ic_print;
                 } else {
-                    return R.drawable.stat_notify_cancelling;
+                    return R.drawable.ic_clear;
                 }
             }
         }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 06fbf9f..59f272f 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -297,7 +297,7 @@
                     + " cannot be null");
         }
 
-        mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
+        mCallingPackageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
 
         if (savedInstanceState == null) {
             MetricsLogger.action(this, MetricsEvent.PRINT_PREVIEW, mCallingPackageName);
@@ -715,7 +715,7 @@
         Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
         intent.setType("application/pdf");
         intent.putExtra(Intent.EXTRA_TITLE, info.getName());
-        intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mCallingPackageName);
 
         try {
             startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 2488f55..50d9c17 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -398,8 +398,8 @@
     <string name="enabled_by_admin" msgid="5302986023578399263">"Activada per l\'administrador"</string>
     <string name="disabled_by_admin" msgid="8505398946020816620">"Desactivada per l\'administrador"</string>
     <string name="disabled" msgid="9206776641295849915">"Desactivat"</string>
-    <string name="external_source_trusted" msgid="2707996266575928037">"Permeses"</string>
-    <string name="external_source_untrusted" msgid="2677442511837596726">"No permeses"</string>
+    <string name="external_source_trusted" msgid="2707996266575928037">"Amb permís"</string>
+    <string name="external_source_untrusted" msgid="2677442511837596726">"Sense permís"</string>
     <string name="install_other_apps" msgid="6986686991775883017">"Instal·lar aplicacions desconegudes"</string>
     <string name="home" msgid="3256884684164448244">"Pàgina d\'inici de configuració"</string>
   <string-array name="battery_labels">
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 3e2afed..ca6fe88 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -320,7 +320,7 @@
     <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"अनुप्रयोगले कुनै मान्य च्यानल बिना सूचना पोस्ट गर्दा स्क्रिनमा चेतावनी देखाउँछ"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"बाह्यमा बल प्रयोगको अनुमति प्राप्त अनुप्रयोगहरू"</string>
     <string name="force_allow_on_external_summary" msgid="3640752408258034689">"म्यानिफेेस्टका मानहरूको ख्याल नगरी कुनै पनि अनुप्रयोगलाई बाह्य भण्डारणमा लेख्न सकिने खाले बनाउँछ"</string>
-    <string name="force_resizable_activities" msgid="8615764378147824985">"गतिविधिहरू रिसाइज गर्नको लागि बाध्य गर्नुहोस्"</string>
+    <string name="force_resizable_activities" msgid="8615764378147824985">"आकार बदल्न योग्य हुने बनाउन गतिविधिहरूलाई बाध्यात्मक बनाउनुहोस्।"</string>
     <string name="force_resizable_activities_summary" msgid="6667493494706124459">"म्यानिफेेस्ट मानहरूको ख्याल नगरी, बहु-विन्डोको लागि सबै रिसाइज गर्न सकिने गतिविधिहरू बनाउनुहोस्।"</string>
     <string name="enable_freeform_support" msgid="1461893351278940416">"फ्रिफर्म विन्डोहरू सक्रिय गर्नुहोस्"</string>
     <string name="enable_freeform_support_summary" msgid="8247310463288834487">"प्रयोगात्मक फ्रिफर्म विन्डोहरूका लागि समर्थन सक्रिय गर्नुहोस्।"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index 7b69c78..54adf82 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -22,7 +22,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <string-array name="wifi_status">
     <item msgid="1922181315419294640"></item>
-    <item msgid="8934131797783724664">"ஸ்கேன் செய்கிறது…"</item>
+    <item msgid="8934131797783724664">"தேடுகிறது..."</item>
     <item msgid="8513729475867537913">"இணைக்கிறது..."</item>
     <item msgid="515055375277271756">"அங்கீகரிக்கிறது..."</item>
     <item msgid="1943354004029184381">"IP முகவரியைப் பெறுகிறது…"</item>
@@ -36,7 +36,7 @@
   </string-array>
   <string-array name="wifi_status_with_ssid">
     <item msgid="7714855332363650812"></item>
-    <item msgid="8878186979715711006">"ஸ்கேன் செய்கிறது…"</item>
+    <item msgid="8878186979715711006">"தேடுகிறது..."</item>
     <item msgid="355508996603873860">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> இல் இணைக்கிறது…"</item>
     <item msgid="554971459996405634">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> உடன் அங்கீகரிக்கிறது…"</item>
     <item msgid="7928343808033020343">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> இலிருந்து IP முகவரியைப் பெறுகிறது…"</item>
@@ -180,30 +180,30 @@
   </string-array>
   <string-array name="window_animation_scale_entries">
     <item msgid="8134156599370824081">"அனிமேஷனை முடக்கு"</item>
-    <item msgid="6624864048416710414">"அனிமேஷன் அளவு .5x"</item>
-    <item msgid="2219332261255416635">"அனிமேஷன் அளவு 1x"</item>
-    <item msgid="3544428804137048509">"அனிமேஷன் அளவு 1.5x"</item>
-    <item msgid="3110710404225974514">"அனிமேஷன் அளவு 2x"</item>
-    <item msgid="4402738611528318731">"அனிமேஷன் அளவு 5x"</item>
-    <item msgid="6189539267968330656">"அனிமேஷன் அளவு 10x"</item>
+    <item msgid="6624864048416710414">"அனிமேஷன் வேகம் .5x"</item>
+    <item msgid="2219332261255416635">"அனிமேஷன் வேகம் 1x"</item>
+    <item msgid="3544428804137048509">"அனிமேஷன் வேகம் 1.5x"</item>
+    <item msgid="3110710404225974514">"அனிமேஷன் வேகம் 2x"</item>
+    <item msgid="4402738611528318731">"அனிமேஷன் வேகம் 5x"</item>
+    <item msgid="6189539267968330656">"அனிமேஷன் வேகம் 10x"</item>
   </string-array>
   <string-array name="transition_animation_scale_entries">
     <item msgid="8464255836173039442">"அனிமேஷனை முடக்கு"</item>
-    <item msgid="3375781541913316411">"அனிமேஷன் அளவு .5x"</item>
-    <item msgid="1991041427801869945">"அனிமேஷன் அளவு 1x"</item>
-    <item msgid="4012689927622382874">"அனிமேஷன் அளவு 1.5x"</item>
-    <item msgid="3289156759925947169">"அனிமேஷன் அளவு 2x"</item>
-    <item msgid="7705857441213621835">"அனிமேஷன் அளவு 5x"</item>
-    <item msgid="6660750935954853365">"அனிமேஷன் அளவு 10x"</item>
+    <item msgid="3375781541913316411">"அனிமேஷன் வேகம் .5x"</item>
+    <item msgid="1991041427801869945">"அனிமேஷன் வேகம் 1x"</item>
+    <item msgid="4012689927622382874">"அனிமேஷன் வேகம் 1.5x"</item>
+    <item msgid="3289156759925947169">"அனிமேஷன் வேகம் 2x"</item>
+    <item msgid="7705857441213621835">"அனிமேஷன் வேகம் 5x"</item>
+    <item msgid="6660750935954853365">"அனிமேஷன் வேகம் 10x"</item>
   </string-array>
   <string-array name="animator_duration_scale_entries">
     <item msgid="6039901060648228241">"அனிமேஷனை முடக்கு"</item>
-    <item msgid="1138649021950863198">"அனிமேஷன் அளவு .5x"</item>
-    <item msgid="4394388961370833040">"அனிமேஷன் அளவு 1x"</item>
-    <item msgid="8125427921655194973">"அனிமேஷன் அளவு 1.5x"</item>
-    <item msgid="3334024790739189573">"அனிமேஷன் அளவு 2x"</item>
-    <item msgid="3170120558236848008">"அனிமேஷன் அளவு 5x"</item>
-    <item msgid="1069584980746680398">"அனிமேஷன் அளவு 10x"</item>
+    <item msgid="1138649021950863198">"அனிமேஷன் வேகம் .5x"</item>
+    <item msgid="4394388961370833040">"அனிமேஷன் வேகம் 1x"</item>
+    <item msgid="8125427921655194973">"அனிமேஷன் வேகம் 1.5x"</item>
+    <item msgid="3334024790739189573">"அனிமேஷன் வேகம் 2x"</item>
+    <item msgid="3170120558236848008">"அனிமேஷன் வேகம் 5x"</item>
+    <item msgid="1069584980746680398">"அனிமேஷன் வேகம் 10x"</item>
   </string-array>
   <string-array name="overlay_display_devices_entries">
     <item msgid="1606809880904982133">"ஏதுமில்லை"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 828e542..5f63fee 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -306,8 +306,8 @@
     <string name="track_frame_time" msgid="6094365083096851167">"சுயவிவர HWUI ரெண்டரிங்"</string>
     <string name="enable_gpu_debug_layers" msgid="3848838293793255097">"GPU பிழைத்திருத்த லேயர்களை இயக்கு"</string>
     <string name="enable_gpu_debug_layers_summary" msgid="8009136940671194940">"பிழைத்திருத்த ஆப்ஸிற்கு, GPU பிழைத்திருத்த லேயர்களை ஏற்றுவதற்கு அனுமதி"</string>
-    <string name="window_animation_scale_title" msgid="6162587588166114700">"சாளர அனிமேஷன் அளவு"</string>
-    <string name="transition_animation_scale_title" msgid="387527540523595875">"அனிமேஷன் மாற்றத்தின் அளவு"</string>
+    <string name="window_animation_scale_title" msgid="6162587588166114700">"சாளர அனிமேஷன் வேகம்"</string>
+    <string name="transition_animation_scale_title" msgid="387527540523595875">"அனிமேஷன் மாற்றத்தின் வேகம்"</string>
     <string name="animator_duration_scale_title" msgid="3406722410819934083">"அனிமேட்டர் கால அளவு"</string>
     <string name="overlay_display_devices_title" msgid="5364176287998398539">"இரண்டாம்நிலைக் காட்சிகளை உருவகப்படுத்து"</string>
     <string name="debug_applications_category" msgid="4206913653849771549">"ஆப்ஸ்"</string>
@@ -358,7 +358,7 @@
     <string name="picture_color_mode" msgid="4560755008730283695">"படத்தின் வண்ணப் பயன்முறை"</string>
     <string name="picture_color_mode_desc" msgid="1141891467675548590">"sRGBஐப் பயன்படுத்தும்"</string>
     <string name="daltonizer_mode_disabled" msgid="7482661936053801862">"முடக்கப்பட்டது"</string>
-    <string name="daltonizer_mode_monochromacy" msgid="8485709880666106721">"மோனோகுரோமசி"</string>
+    <string name="daltonizer_mode_monochromacy" msgid="8485709880666106721">"ஒற்றை நிறத் தன்மை"</string>
     <string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index f9aa062..2bd0b27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -24,20 +24,22 @@
 
 import android.annotation.Nullable;
 import android.app.Activity;
-import androidx.lifecycle.LifecycleOwner;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.view.Menu;
 import android.view.MenuItem;
 
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
+
 /**
  * {@link Activity} that has hooks to observe activity lifecycle events.
  */
-public class ObservableActivity extends Activity implements LifecycleOwner {
+public class ObservableActivity extends FragmentActivity implements LifecycleOwner {
 
     private final Lifecycle mLifecycle = new Lifecycle(this);
 
-    public Lifecycle getLifecycle() {
+    public Lifecycle getSettingsLifecycle() {
         return mLifecycle;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
index 972e062..869f54f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -22,14 +22,15 @@
 import static androidx.lifecycle.Lifecycle.Event.ON_START;
 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.app.DialogFragment;
-import androidx.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.LifecycleOwner;
+
 /**
  * {@link DialogFragment} that has hooks to observe fragment lifecycle events.
  */
@@ -37,6 +38,10 @@
 
     protected final Lifecycle mLifecycle = new Lifecycle(this);
 
+    public Lifecycle getSettingsLifecycle() {
+        return mLifecycle;
+    }
+
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
@@ -100,9 +105,4 @@
         }
         return lifecycleHandled;
     }
-
-    @Override
-    public Lifecycle getLifecycle() {
-        return mLifecycle;
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
index 55597cc..6ba930d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -24,19 +24,20 @@
 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
 import android.annotation.CallSuper;
-import android.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LifecycleOwner;
+
 public class ObservableFragment extends Fragment implements LifecycleOwner {
 
     private final Lifecycle mLifecycle = new Lifecycle(this);
 
-    public Lifecycle getLifecycle() {
+    public Lifecycle getSettingsLifecycle() {
         return mLifecycle;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index 904681c..bd1e5a5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -24,24 +24,25 @@
 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
 import android.annotation.CallSuper;
-import androidx.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.os.Bundle;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
 /**
- * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
+ * {@link PreferenceFragmentCompat} that has hooks to observe fragment lifecycle events.
  */
-public abstract class ObservablePreferenceFragment extends PreferenceFragment
+public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
         implements LifecycleOwner {
 
     private final Lifecycle mLifecycle = new Lifecycle(this);
 
-    public Lifecycle getLifecycle() {
+    public Lifecycle getSettingsLifecycle() {
         return mLifecycle;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index e5d97c9..3c0f6fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -256,11 +256,12 @@
         }
     }
 
-    public void launchSettings(DreamInfo dreamInfo) {
+    public void launchSettings(Context uiContext, DreamInfo dreamInfo) {
         logd("launchSettings(%s)", dreamInfo);
-        if (dreamInfo == null || dreamInfo.settingsComponentName == null)
+        if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
             return;
-        mContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+        }
+        uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
     }
 
     public void preview(DreamInfo dreamInfo) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index 52068e9..28828a0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -18,11 +18,12 @@
 import static androidx.lifecycle.Lifecycle.Event.ON_START;
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.lifecycle.LifecycleOwner;
 import android.content.Context;
+import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.widget.LinearLayout;
 
 import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.events.OnAttach;
@@ -34,13 +35,15 @@
 import com.android.settingslib.core.lifecycle.events.OnResume;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.testutils.FragmentTestUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
 import org.robolectric.android.controller.ActivityController;
-import org.robolectric.android.controller.FragmentController;
+
+import androidx.lifecycle.LifecycleOwner;
 
 @RunWith(SettingsLibRobolectricTestRunner.class)
 public class LifecycleTest {
@@ -64,7 +67,7 @@
 
         public TestFragment() {
             mFragObserver = new TestObserver();
-            getLifecycle().addObserver(mFragObserver);
+            getSettingsLifecycle().addObserver(mFragObserver);
         }
     }
 
@@ -74,9 +77,17 @@
 
         public TestActivity() {
             mActObserver = new TestObserver();
-            getLifecycle().addObserver(mActObserver);
+            getSettingsLifecycle().addObserver(mActObserver);
         }
 
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LinearLayout view = new LinearLayout(this);
+            view.setId(1);
+
+            setContentView(view);
+        }
     }
 
     public static class TestObserver implements LifecycleObserver, OnAttach, OnStart, OnResume,
@@ -151,11 +162,9 @@
     @Test
     public void runThroughActivityLifecycles_shouldObserveEverything() {
         ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
-        TestActivity activity = ac.get();
+        TestActivity activity = ac.setup().get();
 
-        ac.start();
         assertThat(activity.mActObserver.mOnStartObserved).isTrue();
-        ac.resume();
         assertThat(activity.mActObserver.mOnResumeObserved).isTrue();
         activity.onCreateOptionsMenu(null);
         assertThat(activity.mActObserver.mOnCreateOptionsMenuObserved).isTrue();
@@ -173,50 +182,50 @@
 
     @Test
     public void runThroughDialogFragmentLifecycles_shouldObserveEverything() {
-        FragmentController<TestDialogFragment> fragmentController =
-                Robolectric.buildFragment(TestDialogFragment.class);
-        TestDialogFragment fragment = fragmentController.get();
+        final TestDialogFragment fragment = new TestDialogFragment();
+        FragmentTestUtils.startFragment(fragment);
 
-        fragmentController.create().start().resume();
         fragment.onCreateOptionsMenu(null, null);
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
-        fragmentController.pause().stop().destroy();
+        assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+        assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+        assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
 
         assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
         assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
         assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
         assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+        fragment.onPause();
         assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+        fragment.onStop();
         assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+        fragment.onDestroy();
         assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
-        assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
-        assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
-        assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
     }
 
     @Test
     public void runThroughFragmentLifecycles_shouldObserveEverything() {
-        FragmentController<TestFragment> fragmentController =
-                Robolectric.buildFragment(TestFragment.class);
-        TestFragment fragment = fragmentController.get();
+        final TestFragment fragment = new TestFragment();
+        FragmentTestUtils.startFragment(fragment);
 
-        fragmentController.create().start().resume();
         fragment.onCreateOptionsMenu(null, null);
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
-        fragmentController.pause().stop().destroy();
+        assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+        assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+        assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
 
         assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
         assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
         assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
         assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+        fragment.onPause();
         assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+        fragment.onStop();
         assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+        fragment.onDestroy();
         assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
-        assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
-        assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
-        assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
     }
 
     @Test
@@ -237,17 +246,16 @@
 
     @Test
     public void onOptionItemSelectedShortCircuitsIfAnObserverHandlesTheMenuItem() {
-        FragmentController<TestFragment> fragmentController =
-                Robolectric.buildFragment(TestFragment.class);
-        TestFragment fragment = fragmentController.get();
-        OptionItemAccepter accepter = new OptionItemAccepter();
+        final TestFragment fragment = new TestFragment();
+        FragmentTestUtils.startFragment(fragment);
+
+        final OptionItemAccepter accepter = new OptionItemAccepter();
         fragment.getLifecycle().addObserver(accepter);
 
-        fragmentController.create().start().resume();
+
         fragment.onCreateOptionsMenu(null, null);
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
-        fragmentController.pause().stop().destroy();
 
         assertThat(accepter.wasCalled).isFalse();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java
new file mode 100644
index 0000000..8ba8606
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.settingslib.testutils;
+
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import org.robolectric.Robolectric;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+/**
+ * Utilities for creating Fragments for testing.
+ * <p>
+ * TODO(b/111195449) - Duplicated from org.robolectric.shadows.support.v4.SupportFragmentTestUtil
+ */
+@Deprecated
+public class FragmentTestUtils {
+
+    public static void startFragment(Fragment fragment) {
+        buildFragmentManager(FragmentUtilActivity.class)
+                .beginTransaction().add(fragment, null).commit();
+    }
+
+    public static void startFragment(Fragment fragment,
+            Class<? extends FragmentActivity> activityClass) {
+        buildFragmentManager(activityClass)
+                .beginTransaction().add(fragment, null).commit();
+    }
+
+    public static void startVisibleFragment(Fragment fragment) {
+        buildFragmentManager(FragmentUtilActivity.class)
+                .beginTransaction().add(1, fragment, null).commit();
+    }
+
+    public static void startVisibleFragment(Fragment fragment,
+            Class<? extends FragmentActivity> activityClass, int containerViewId) {
+        buildFragmentManager(activityClass)
+                .beginTransaction().add(containerViewId, fragment, null).commit();
+    }
+
+    private static FragmentManager buildFragmentManager(
+            Class<? extends FragmentActivity> activityClass) {
+        FragmentActivity activity = Robolectric.setupActivity(activityClass);
+        return activity.getSupportFragmentManager();
+    }
+
+    private static class FragmentUtilActivity extends FragmentActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            LinearLayout view = new LinearLayout(this);
+            view.setId(1);
+
+            setContentView(view);
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 375fef8a..9592b63 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2935,7 +2935,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 169;
+            private static final int SETTINGS_VERSION = 170;
 
             private final int mUserId;
 
@@ -3810,6 +3810,37 @@
                     currentVersion = 169;
                 }
 
+                if (currentVersion == 169) {
+                    // Version 169: by default, add STREAM_VOICE_CALL to list of streams that can
+                    // be muted.
+                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
+                    final Setting currentSetting = systemSettings.getSettingLocked(
+                              Settings.System.MUTE_STREAMS_AFFECTED);
+                    if (!currentSetting.isNull()) {
+                        try {
+                            int currentSettingIntegerValue = Integer.parseInt(
+                                    currentSetting.getValue());
+                            if ((currentSettingIntegerValue
+                                 & (1 << AudioManager.STREAM_VOICE_CALL)) == 0) {
+                                systemSettings.insertSettingLocked(
+                                    Settings.System.MUTE_STREAMS_AFFECTED,
+                                    Integer.toString(
+                                        currentSettingIntegerValue
+                                        | (1 << AudioManager.STREAM_VOICE_CALL)),
+                                    null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                            }
+                        } catch (NumberFormatException e) {
+                            // remove the setting in case it is not a valid integer
+                            Slog.w("Failed to parse integer value of MUTE_STREAMS_AFFECTED"
+                                   + "setting, removing setting", e);
+                            systemSettings.deleteSettingLocked(
+                                Settings.System.MUTE_STREAMS_AFFECTED);
+                        }
+
+                    }
+                    currentVersion = 170;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/color/notification_guts_buttons.xml b/packages/SystemUI/res/color/notification_guts_buttons.xml
index 3b8d59b..412e0be 100644
--- a/packages/SystemUI/res/color/notification_guts_buttons.xml
+++ b/packages/SystemUI/res/color/notification_guts_buttons.xml
@@ -2,6 +2,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_checked="true"
           android:color="?android:attr/colorAccent" />
-    <item android:color="@android:color/black"
+    <item android:color="@color/notification_primary_text_color"
           android:alpha=".54" />
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index ea6ef4c..55e7a85 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -36,7 +36,7 @@
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
             android:paddingStart="@*android:dimen/notification_content_margin_start"
-            android:textColor="#DD000000"
+            android:textColor="@color/notification_primary_text_color"
             android:paddingEnd="4dp"/>
 
         <ImageView
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
new file mode 100644
index 0000000..45d2185
--- /dev/null
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+
+  NOTE: You might also want to edit: core/res/res/values-night/*.xml
+  -->
+<resources>
+    <!-- The color of the material notification background -->
+    <color name="notification_material_background_color">@*android:color/notification_material_background_color</color>
+
+    <!-- The color of the legacy notifications with customs backgrounds (gingerbread and lollipop.)
+    It's fine to override this color since at that point the shade was dark. -->
+    <color name="notification_legacy_background_color">@*android:color/notification_material_background_color</color>
+
+    <!-- The color of the material notification background when dimmed -->
+    <color name="notification_material_background_dimmed_color">#aa212121</color>
+
+    <!-- The color of the dividing line between grouped notifications while . -->
+    <color name="notification_divider_color">#000</color>
+
+    <!-- The background color of the notification shade -->
+    <color name="notification_shade_background_color">#181818</color>
+
+    <!-- The color of the ripples on the untinted notifications -->
+    <color name="notification_ripple_untinted_color">#30ffffff</color>
+
+    <!-- The "inside" of a notification, reached via longpress -->
+    <color name="notification_guts_bg_color">@*android:color/notification_material_background_color</color>
+
+    <!-- The color of the text inside a notification -->
+    <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml
new file mode 100644
index 0000000..4814839
--- /dev/null
+++ b/packages/SystemUI/res/values-night/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<resources>
+    <!-- The height of the divider between the individual notifications. -->
+    <dimen name="notification_divider_height">1dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 07f1ee0..4920fb2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -94,6 +94,9 @@
     <!-- The color of the gear shown behind a notification -->
     <color name="notification_gear_color">#ff757575</color>
 
+    <!-- The color of the text inside a notification -->
+    <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_light</color>
+
     <!-- The "inside" of a notification, reached via longpress -->
     <color name="notification_guts_bg_color">#f8f9fa</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f62249a..a9d995c8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -252,6 +252,9 @@
          -->
     <dimen name="qs_header_system_icons_area_height">48dp</dimen>
 
+    <!-- How far the quick-quick settings panel extends below the status bar -->
+    <dimen name="qs_quick_header_panel_height">128dp</dimen>
+
     <!-- The height of the container that holds the system icons in the quick settings header in the
          car setting. -->
     <dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f867048..5a03c4a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -452,23 +452,20 @@
 
     <style name="TextAppearance.NotificationInfo">
         <item name="android:fontFamily">sans-serif</item>
-        <item name="android:textColor">@android:color/black</item>
+        <item name="android:textColor">@color/notification_primary_text_color</item>
     </style>
 
     <style name="TextAppearance.NotificationInfo.Primary">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">16sp</item>
         <item name="android:alpha">0.87</item>
     </style>
 
     <style name="TextAppearance.NotificationInfo.Confirmation">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:alpha">0.87</item>
     </style>
 
     <style name="TextAppearance.NotificationInfo.Secondary">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:alpha">0.54</item>
     </style>
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 6801e69..9a648d1 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -77,6 +77,7 @@
     private int mPlugType = 0;
     private int mInvalidCharger = 0;
     private EnhancedEstimates mEnhancedEstimates;
+    private Estimate mLastEstimate;
     private boolean mLowWarningShownThisChargeCycle;
     private boolean mSevereWarningShownThisChargeCycle;
 
@@ -247,7 +248,8 @@
 
                 // Show the correct version of low battery warning if needed
                 ThreadUtils.postOnBackgroundThread(() -> {
-                    maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
+                    maybeShowBatteryWarning(
+                            oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
                 });
 
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -262,14 +264,18 @@
         }
     }
 
-    protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
-        int bucket) {
+    protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
+            int oldBucket, int bucket) {
         boolean isPowerSaver = mPowerManager.isPowerSaveMode();
         // only play SFX when the dialog comes up or the bucket changes
         final boolean playSound = bucket != oldBucket || oldPlugged;
         final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
         if (hybridEnabled) {
-            final Estimate estimate = mEnhancedEstimates.getEstimate();
+            Estimate estimate = mLastEstimate;
+            if (estimate == null || mBatteryLevel != oldBatteryLevel) {
+                estimate = mEnhancedEstimates.getEstimate();
+                mLastEstimate = estimate;
+            }
             // Turbo is not always booted once SysUI is running so we have ot make sure we actually
             // get data back
             if (estimate != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index a218868..35d2f90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -33,6 +33,7 @@
 import android.os.Handler;
 import android.provider.AlarmClock;
 import android.service.notification.ZenModeConfig;
+import android.widget.FrameLayout;
 import androidx.annotation.VisibleForTesting;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
@@ -270,8 +271,22 @@
         updateResources();
     }
 
+    /**
+     * The height of QQS should always be the status bar height + 128dp. This is normally easy, but
+     * when there is a notch involved the status bar can remain a fixed pixel size.
+     */
+    private void updateMinimumHeight() {
+        int sbHeight = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        int qqsHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.qs_quick_header_panel_height);
+
+        setMinimumHeight(sbHeight + qqsHeight);
+    }
+
     private void updateResources() {
         Resources resources = mContext.getResources();
+        updateMinimumHeight();
 
         // Update height for a few views, especially due to landscape mode restricting space.
         mHeaderTextContainerView.getLayoutParams().height =
@@ -282,10 +297,17 @@
                 com.android.internal.R.dimen.quick_qs_offset_height);
         mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
 
-        getLayoutParams().height = resources.getDimensionPixelSize(mQsDisabled
-                ? com.android.internal.R.dimen.quick_qs_offset_height
-                : com.android.internal.R.dimen.quick_qs_total_height);
-        setLayoutParams(getLayoutParams());
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        if (mQsDisabled) {
+            lp.height = resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.quick_qs_offset_height);
+        } else {
+            lp.height = Math.max(getMinimumHeight(),
+                    resources.getDimensionPixelSize(
+                            com.android.internal.R.dimen.quick_qs_offset_height));
+        }
+
+        setLayoutParams(lp);
 
         updateStatusIconAlphaAnimator();
         updateHeaderTextContainerAlphaAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 0ea941f..5eaee54 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -301,11 +301,11 @@
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
 
             // Create a share action for the notification
-            PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
+            PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
                     new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
                             .putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
                             .putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
-                    PendingIntent.FLAG_CANCEL_CURRENT);
+                    PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
             Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
                     R.drawable.ic_screenshot_share,
                     r.getString(com.android.internal.R.string.share), shareAction);
@@ -324,11 +324,11 @@
             editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 
             // Create a edit action
-            PendingIntent editAction = PendingIntent.getBroadcast(context, 1,
+            PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
                     new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
                             .putExtra(EXTRA_ACTION_INTENT, editIntent)
                             .putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
-                    PendingIntent.FLAG_CANCEL_CURRENT);
+                    PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
             Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
                     R.drawable.ic_screenshot_edit,
                     r.getString(com.android.internal.R.string.screenshot_edit), editAction);
@@ -910,7 +910,7 @@
                 ActivityOptions opts = ActivityOptions.makeBasic();
                 opts.setDisallowEnterPictureInPictureWhileLaunching(
                         intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
-                context.startActivityAsUser(actionIntent, opts.toBundle(),UserHandle.CURRENT);
+                context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
             };
             StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
             statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 6a38797..acdfa29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -98,8 +98,8 @@
             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
             = new PathInterpolator(0, 0, 0.5f, 1);
-    private final int mTintedRippleColor;
-    protected final int mNormalRippleColor;
+    private int mTintedRippleColor;
+    protected int mNormalRippleColor;
     private final AccessibilityManager mAccessibilityManager;
     private final DoubleTapHelper mDoubleTapHelper;
 
@@ -132,7 +132,7 @@
     private ValueAnimator mBackgroundColorAnimator;
     private float mAppearAnimationFraction = -1.0f;
     private float mAppearAnimationTranslation;
-    private final int mNormalColor;
+    private int mNormalColor;
     private boolean mIsBelowSpeedBump;
     private FalsingManager mFalsingManager;
 
@@ -188,11 +188,7 @@
         mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
         setClipChildren(false);
         setClipToPadding(false);
-        mNormalColor = context.getColor(R.color.notification_material_background_color);
-        mTintedRippleColor = context.getColor(
-                R.color.notification_ripple_tinted_color);
-        mNormalRippleColor = context.getColor(
-                R.color.notification_ripple_untinted_color);
+        updateColors();
         mFalsingManager = FalsingManager.getInstance(context);
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -206,6 +202,16 @@
         initDimens();
     }
 
+    private void updateColors() {
+        mNormalColor = mContext.getColor(R.color.notification_material_background_color);
+        mTintedRippleColor = mContext.getColor(
+                R.color.notification_ripple_tinted_color);
+        mNormalRippleColor = mContext.getColor(
+                R.color.notification_ripple_untinted_color);
+        mDimmedAlpha = Color.alpha(mContext.getColor(
+                R.color.notification_material_background_dimmed_color));
+    }
+
     private void initDimens() {
         mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_content_margin_start);
@@ -217,6 +223,12 @@
         initDimens();
     }
 
+    public void onUiModeChanged() {
+        updateColors();
+        initBackground();
+        updateBackgroundTint();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -224,8 +236,6 @@
         mFakeShadow = findViewById(R.id.fake_shadow);
         mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
         mBackgroundDimmed = findViewById(R.id.backgroundDimmed);
-        mDimmedAlpha = Color.alpha(mContext.getColor(
-                R.color.notification_material_background_dimmed_color));
         initBackground();
         updateBackground();
         updateBackgroundTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9393d5b..1d3f408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -24,6 +24,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -1053,6 +1054,10 @@
         super.onDensityOrFontScaleChanged();
         initDimens();
         initBackground();
+        reInflateViews();
+    }
+
+    private void reInflateViews() {
         // Let's update our childrencontainer. This is intentionally not guarded with
         // mIsSummaryWithChildren since we might have had children but not anymore.
         if (mChildrenContainer != null) {
@@ -1079,7 +1084,7 @@
             l.initView();
             l.reInflateViews();
         }
-        mNotificationInflater.onDensityOrFontScaleChanged();
+        mNotificationInflater.clearCachesAndReInflate();
         onNotificationUpdated();
     }
 
@@ -1090,6 +1095,17 @@
         }
     }
 
+    @Override
+    public void onUiModeChanged() {
+        super.onUiModeChanged();
+        reInflateViews();
+        if (mChildrenContainer != null) {
+            for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
+                child.onUiModeChanged();
+            }
+        }
+    }
+
     public void setContentBackground(int customBackgroundColor, boolean animate,
             NotificationContentView notificationContentView) {
         if (getShowingLayout() == notificationContentView) {
@@ -1448,6 +1464,10 @@
         mNotificationInflater.setUsesIncreasedHeight(use);
     }
 
+    public void setSmartActions(List<Notification.Action> smartActions) {
+        mNotificationInflater.setSmartActions(smartActions);
+    }
+
     public void setUseIncreasedHeadsUpHeight(boolean use) {
         mUseIncreasedHeadsUpHeight = use;
         mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index a58752c..93433da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -28,6 +28,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -51,6 +52,8 @@
 import android.widget.ImageView;
 import android.widget.RemoteViews;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
@@ -105,6 +108,8 @@
         public CharSequence remoteInputText;
         public List<SnoozeCriterion> snoozeCriteria;
         public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+        @NonNull
+        public List<Notification.Action> smartActions = Collections.emptyList();
 
         private int mCachedContrastColor = COLOR_INVALID;
         private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -131,8 +136,23 @@
         private boolean hasSentReply;
 
         public Entry(StatusBarNotification n) {
+            this(n, null);
+        }
+
+        public Entry(StatusBarNotification n, @Nullable Ranking ranking) {
             this.key = n.getKey();
             this.notification = n;
+            if (ranking != null) {
+                populateFromRanking(ranking);
+            }
+        }
+
+        public void populateFromRanking(@NonNull Ranking ranking) {
+            channel = ranking.getChannel();
+            snoozeCriteria = ranking.getSnoozeCriteria();
+            userSentiment = ranking.getUserSentiment();
+            smartActions = ranking.getSmartActions() == null
+                    ? Collections.emptyList() : ranking.getSmartActions();
         }
 
         public void setInterruption() {
@@ -232,6 +252,7 @@
 
         /**
          * Update the notification icons.
+         *
          * @param context the context to create the icons with.
          * @param sbn the notification to read the icon from.
          * @throws InflationException
@@ -291,7 +312,7 @@
         }
 
         public void onInflationTaskFinished() {
-           mRunningTask = null;
+            mRunningTask = null;
         }
 
         @VisibleForTesting
@@ -607,7 +628,7 @@
             getRanking(key, mTmpRanking);
             return mTmpRanking.getOverrideGroupKey();
         }
-         return null;
+        return null;
     }
 
     public List<SnoozeCriterion> getSnoozeCriteria(String key) {
@@ -658,9 +679,7 @@
                         entry.notification.setOverrideGroupKey(overrideGroupKey);
                         mGroupManager.onEntryUpdated(entry, oldSbn);
                     }
-                    entry.channel = getChannel(entry.key);
-                    entry.snoozeCriteria = getSnoozeCriteria(entry.key);
-                    entry.userSentiment = mTmpRanking.getUserSentiment();
+                    entry.populateFromRanking(mTmpRanking);
                 }
             }
         }
@@ -833,6 +852,7 @@
         public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
         public String getCurrentMediaNotificationKey();
         public NotificationGroupManager getGroupManager();
+
         /**
          * @return true iff the device is dozing
          */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
index 06f26c9..bf07929 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.statusbar.NotificationRemoteInputManager
         .FORCE_REMOTE_INPUT_HISTORY;
 
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -37,6 +38,7 @@
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
@@ -726,10 +728,10 @@
                 && !mPresenter.isPresenterFullyCollapsed();
         row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
         row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        row.setSmartActions(entry.smartActions);
         row.updateNotification(entry);
     }
 
-
     protected void addNotificationViews(NotificationData.Entry entry) {
         if (entry == null) {
             return;
@@ -740,12 +742,13 @@
         updateNotifications();
     }
 
-    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+    protected NotificationData.Entry createNotificationViews(
+            StatusBarNotification sbn, NotificationListenerService.Ranking ranking)
             throws InflationException {
         if (DEBUG) {
-            Log.d(TAG, "createNotificationViews(notification=" + sbn);
+            Log.d(TAG, "createNotificationViews(notification=" + sbn + " " + ranking);
         }
-        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking);
         Dependency.get(LeakDetector.class).trackInstance(entry);
         entry.createIcons(mContext, sbn);
         // Construct the expanded view.
@@ -754,12 +757,14 @@
     }
 
     private void addNotificationInternal(StatusBarNotification notification,
-            NotificationListenerService.RankingMap ranking) throws InflationException {
+            NotificationListenerService.RankingMap rankingMap) throws InflationException {
         String key = notification.getKey();
         if (DEBUG) Log.d(TAG, "addNotification key=" + key);
 
-        mNotificationData.updateRanking(ranking);
-        NotificationData.Entry shadeEntry = createNotificationViews(notification);
+        mNotificationData.updateRanking(rankingMap);
+        NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+        rankingMap.getRanking(key, ranking);
+        NotificationData.Entry shadeEntry = createNotificationViews(notification, ranking);
         boolean isHeadsUped = shouldPeek(shadeEntry);
         if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
             if (shouldSuppressFullScreenIntent(shadeEntry)) {
@@ -905,11 +910,57 @@
         mPresenter.updateNotificationViews();
     }
 
-    public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
-        mNotificationData.updateRanking(ranking);
+    public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
+        List<NotificationData.Entry> entries = new ArrayList<>();
+        entries.addAll(mNotificationData.getActiveNotifications());
+        entries.addAll(mPendingNotifications.values());
+
+        // Has a copy of the current UI adjustments.
+        ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
+        for (NotificationData.Entry entry : entries) {
+            NotificationUiAdjustment adjustment =
+                    NotificationUiAdjustment.extractFromNotificationEntry(entry);
+            oldAdjustments.put(entry.key, adjustment);
+        }
+
+        // Populate notification entries from the new rankings.
+        mNotificationData.updateRanking(rankingMap);
+        updateRankingOfPendingNotifications(rankingMap);
+
+        // By comparing the old and new UI adjustments, reinflate the view accordingly.
+        for (NotificationData.Entry entry : entries) {
+            NotificationUiAdjustment newAdjustment =
+                    NotificationUiAdjustment.extractFromNotificationEntry(entry);
+
+            if (NotificationUiAdjustment.needReinflate(
+                    oldAdjustments.get(entry.key), newAdjustment)) {
+                if (entry.row != null) {
+                    entry.reset();
+                    PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                            entry.notification.getUser().getIdentifier());
+                    updateNotification(entry, pmUser, entry.notification, entry.row);
+                } else {
+                    // Once the RowInflaterTask is done, it will pick up the updated entry, so
+                    // no-op here.
+                }
+            }
+        }
+
         updateNotifications();
     }
 
+    private void updateRankingOfPendingNotifications(
+            @Nullable NotificationListenerService.RankingMap rankingMap) {
+        if (rankingMap == null) {
+            return;
+        }
+        NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
+        for (NotificationData.Entry pendingNotification : mPendingNotifications.values()) {
+            rankingMap.getRanking(pendingNotification.key, tmpRanking);
+            pendingNotification.populateFromRanking(tmpRanking);
+        }
+    }
+
     protected boolean shouldPeek(NotificationData.Entry entry) {
         return shouldPeek(entry, entry.notification);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 886d6f1..e013ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar;
 
 import android.app.Notification;
+import android.content.res.Configuration;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
@@ -25,6 +26,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.util.ContrastColorUtil;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -74,8 +77,11 @@
                 imageView.getDrawable().mutate();
                 if (shouldApply) {
                     // lets gray it out
-                    int grey = view.getContext().getColor(
-                            com.android.internal.R.color.notification_default_color_light);
+                    Configuration config = view.getContext().getResources().getConfiguration();
+                    boolean inNightMode = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                            == Configuration.UI_MODE_NIGHT_YES;
+                    int grey = ContrastColorUtil.resolveColor(view.getContext(),
+                            Notification.COLOR_DEFAULT, inNightMode);
                     imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
                 } else {
                     // lets reset it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
new file mode 100644
index 0000000..e6bdb26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * By diffing two entries, determines is view reinflation needed.
+ */
+public class NotificationUiAdjustment {
+
+    public final String key;
+    public final List<Notification.Action> smartActions;
+
+    @VisibleForTesting
+    NotificationUiAdjustment(String key, List<Notification.Action> smartActions) {
+        this.key = key;
+        this.smartActions = smartActions == null
+                ? Collections.emptyList()
+                : new ArrayList<>(smartActions);
+    }
+
+    public static NotificationUiAdjustment extractFromNotificationEntry(
+            NotificationData.Entry entry) {
+        return new NotificationUiAdjustment(entry.key, entry.smartActions);
+    }
+
+    public static boolean needReinflate(
+            @NonNull NotificationUiAdjustment oldAdjustment,
+            @NonNull NotificationUiAdjustment newAdjustment) {
+        if (oldAdjustment == newAdjustment) {
+            return false;
+        }
+        return areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions);
+    }
+
+    public static boolean areDifferent(
+            @NonNull List<Notification.Action> first, @NonNull List<Notification.Action> second) {
+        if (first == second) {
+            return false;
+        }
+        if (first == null || second == null) {
+            return true;
+        }
+        if (first.size() != second.size()) {
+            return true;
+        }
+        for (int i = 0; i < first.size(); i++) {
+            Notification.Action firstAction = first.get(i);
+            Notification.Action secondAction = second.get(i);
+
+            if (!TextUtils.equals(firstAction.title, secondAction.title)) {
+                return true;
+            }
+
+            if (areDifferent(firstAction.getIcon(), secondAction.getIcon())) {
+                return true;
+            }
+
+            if (!Objects.equals(firstAction.actionIntent, secondAction.actionIntent)) {
+                return true;
+            }
+
+            if (areDifferent(firstAction.getRemoteInputs(), secondAction.getRemoteInputs())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean areDifferent(@Nullable Icon first, @Nullable Icon second) {
+        if (first == second) {
+            return false;
+        }
+        if (first == null || second == null) {
+            return true;
+        }
+        return !first.sameAs(second);
+    }
+
+    private static boolean areDifferent(
+            @Nullable RemoteInput[] first, @Nullable RemoteInput[] second) {
+        if (first == second) {
+            return false;
+        }
+        if (first == null || second == null) {
+            return true;
+        }
+        if (first.length != second.length) {
+            return true;
+        }
+        for (int i = 0; i < first.length; i++) {
+            RemoteInput firstRemoteInput = first[i];
+            RemoteInput secondRemoteInput = second[i];
+
+            if (!TextUtils.equals(firstRemoteInput.getLabel(), secondRemoteInput.getLabel())) {
+                return true;
+            }
+            if (areDifferent(firstRemoteInput.getChoices(), secondRemoteInput.getChoices())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean areDifferent(
+            @Nullable CharSequence[] first, @Nullable CharSequence[] second) {
+        if (first == second) {
+            return false;
+        }
+        if (first == null || second == null) {
+            return true;
+        }
+        if (first.length != second.length) {
+            return true;
+        }
+        for (int i = 0; i < first.length; i++) {
+            CharSequence firstCharSequence = first[i];
+            CharSequence secondCharSequence = second[i];
+            if (!TextUtils.equals(firstCharSequence, secondCharSequence)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c820e2b..cc5fbe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -661,8 +661,9 @@
             if (hsl[1] < 0.2f) {
                 contrastedColor = Notification.COLOR_DEFAULT;
             }
+            boolean isDark = !ContrastColorUtil.isColorLight(mCachedContrastBackgroundColor);
             contrastedColor = ContrastColorUtil.resolveContrastColor(mContext,
-                    contrastedColor, mCachedContrastBackgroundColor);
+                    contrastedColor, mCachedContrastBackgroundColor, isDark);
         }
         mContrastedDrawableColor = contrastedColor;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
index 1303057..b9740d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -27,15 +27,17 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationContentView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.Assert;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
@@ -67,6 +69,7 @@
     private boolean mIsChildInGroup;
     private InflationCallback mCallback;
     private boolean mRedactAmbient;
+    private List<Notification.Action> mSmartActions;
 
     public NotificationInflater(ExpandableNotificationRow row) {
         mRow = row;
@@ -95,6 +98,10 @@
         mUsesIncreasedHeight = usesIncreasedHeight;
     }
 
+    public void setSmartActions(List<Notification.Action> smartActions) {
+        mSmartActions = smartActions;
+    }
+
     public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
         mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
     }
@@ -140,7 +147,7 @@
         AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow,
                 mIsLowPriority,
                 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
-                mCallback, mRemoteViewClickHandler);
+                mCallback, mRemoteViewClickHandler, mSmartActions);
         if (mCallback != null && mCallback.doInflateSynchronous()) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -554,7 +561,7 @@
         }
     }
 
-    public void onDensityOrFontScaleChanged() {
+    public void clearCachesAndReInflate() {
         NotificationData.Entry entry = mRow.getEntry();
         entry.cachedAmbientContentView = null;
         entry.cachedBigContentView = null;
@@ -586,13 +593,15 @@
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
+        private List<Notification.Action> mSmartActions;
 
         private AsyncInflationTask(StatusBarNotification notification,
                 int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority,
                 boolean isChildInGroup, boolean usesIncreasedHeight,
                 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
                 InflationCallback callback,
-                RemoteViews.OnClickHandler remoteViewClickHandler) {
+                RemoteViews.OnClickHandler remoteViewClickHandler,
+                List<Notification.Action> smartActions) {
             mRow = row;
             mSbn = notification;
             mReInflateFlags = reInflateFlags;
@@ -604,6 +613,9 @@
             mRedactAmbient = redactAmbient;
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
+            mSmartActions = smartActions == null
+                    ? Collections.emptyList()
+                    : new ArrayList<>(smartActions);
             NotificationData.Entry entry = row.getEntry();
             entry.setInflationTask(this);
         }
@@ -619,6 +631,9 @@
                 final Notification.Builder recoveredBuilder
                         = Notification.Builder.recoverBuilder(mContext,
                         mSbn.getNotification());
+
+                applyChanges(recoveredBuilder);
+
                 Context packageContext = mSbn.getPackageContext(mContext);
                 Notification notification = mSbn.getNotification();
                 if (notification.isMediaNotification()) {
@@ -646,6 +661,18 @@
             }
         }
 
+        /**
+         * Apply changes to the given notification builder, like adding smart actions suggested by
+         * a {@link android.service.notification.NotificationAssistantService}.
+         */
+        private void applyChanges(Notification.Builder builder) {
+            if (mSmartActions != null) {
+                for (Notification.Action smartAction : mSmartActions) {
+                    builder.addAction(smartAction);
+                }
+            }
+        }
+
         private void handleError(Exception e) {
             mRow.getEntry().onInflationTaskFinished();
             StatusBarNotification sbn = mRow.getStatusBarNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index ea70ebb..a781be6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -189,6 +189,14 @@
             state |= DISABLE_SYSTEM_INFO;
             state |= DISABLE_CLOCK;
         }
+
+        // In landscape, the heads up show but shouldHideNotificationIcons() return false
+        // because the visual icon is in notification icon area rather than heads up's space.
+        // whether the notification icon show or not, clock should hide when heads up show.
+        if (mStatusBarComponent.isHeadsUpShouldBeVisible()) {
+            state |= DISABLE_CLOCK;
+        }
+
         if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
             if (mNetworkController.hasEmergencyCryptKeeperText()) {
                 state |= DISABLE_NOTIFICATION_ICONS;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 5a07dbd..240d467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2533,7 +2533,8 @@
     }
 
     private void updateStatusBarIcons() {
-        boolean showIconsWhenExpanded = isFullWidth() && getExpandedHeight() < getOpeningHeight();
+        boolean showIconsWhenExpanded = (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+                && getExpandedHeight() < getOpeningHeight();
         if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
             showIconsWhenExpanded = false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f573642..5b42d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -113,6 +113,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
     private final AlarmTimeout mTimeTicker;
+    private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -171,6 +172,8 @@
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", new Handler());
@@ -892,6 +895,16 @@
         for (ScrimState state : ScrimState.values()) {
             state.setHasBackdrop(hasBackdrop);
         }
+
+        // Backdrop event may arrive after state was already applied,
+        // in this case, back-scrim needs to be re-evaluated
+        if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
+            float newBehindAlpha = mState.getBehindAlpha(mNotificationDensity);
+            if (mCurrentBehindAlpha != newBehindAlpha) {
+                mCurrentBehindAlpha = newBehindAlpha;
+                updateScrims();
+            }
+        }
     }
 
     public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
@@ -910,4 +923,16 @@
         default void onCancelled() {
         }
     }
+
+    /**
+     * Simple keyguard callback that updates scrims when keyguard visibility changes.
+     */
+    private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean showing) {
+            mNeedsDrawableColorUpdate = true;
+            scheduleUpdate();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 19015fc..081ebfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -105,7 +105,6 @@
         public void prepare(ScrimState previousState) {
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
             mBlankScreen = mDisplayRequiresBlanking;
-            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
             mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
             mCurrentInFrontTint = Color.BLACK;
             mCurrentBehindTint = Color.BLACK;
@@ -116,6 +115,11 @@
         }
 
         @Override
+        public float getBehindAlpha(float busyness) {
+            return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
+        }
+
+        @Override
         public boolean isLowPowerState() {
             return true;
         }
@@ -129,10 +133,14 @@
         public void prepare(ScrimState previousState) {
             mCurrentInFrontAlpha = 0;
             mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
             mCurrentBehindTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
         }
+
+        @Override
+        public float getBehindAlpha(float busyness) {
+            return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
+        }
     },
 
     /**
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 7cf2c33..8d077f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1198,6 +1198,9 @@
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.onUiModeChanged();
         }
+        if (mStackScroller != null) {
+            mStackScroller.onUiModeChanged();
+        }
     }
 
     private void inflateEmptyShadeView() {
@@ -2155,6 +2158,10 @@
         }
     }
 
+    public boolean isHeadsUpShouldBeVisible() {
+        return mHeadsUpAppearanceController.shouldBeVisible();
+    }
+
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 6fc4911..eb3289b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -514,6 +514,20 @@
         mSwipeHelper.onMenuShown(row);
     }
 
+    public void onUiModeChanged() {
+        mBgColor = mContext.getColor(R.color.notification_shade_background_color);
+        updateBackgroundDimming();
+
+        // Re-inflate all notification views
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child instanceof ActivatableNotificationView) {
+                ((ActivatableNotificationView) child).onUiModeChanged();
+            }
+        }
+    }
+
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground
                 && (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) {
@@ -588,8 +602,8 @@
 
         // Interpolate between semi-transparent notification panel background color
         // and white AOD separator.
-        float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(
-                mInterpolatedDarkAmount);
+        float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
+                mLinearDarkAmount);
         int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);
 
         if (mCachedBackgroundColor != color) {
@@ -1425,7 +1439,8 @@
      */
     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
         return positionInLinearLayout + v.getIntrinsicHeight() +
-                getImeInset() - getHeight() + getTopPadding();
+                getImeInset() - getHeight()
+                + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
     }
 
     @Override
@@ -2056,9 +2071,15 @@
     }
 
     private int getScrollRange() {
-        int scrollRange = Math.max(0, mContentHeight - mMaxLayoutHeight);
+        // In current design, it only use the top HUN to treat all of HUNs
+        // although there are more than one HUNs
+        int contentHeight = mContentHeight;
+        if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
+            contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
+        }
+        int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
         int imeInset = getImeInset();
-        scrollRange += Math.min(imeInset, Math.max(0, mContentHeight - (getHeight() - imeInset)));
+        scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
         return scrollRange;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index ee006d3..886bd5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -473,6 +473,15 @@
                     childState.yTranslation = topState.yTranslation + topState.height
                             - childState.height;
                 }
+
+                // heads up notification show and this row is the top entry of heads up
+                // notifications. i.e. this row should be the only one row that has input field
+                // To check if the row need to do translation according to scroll Y
+                // heads up show full of row's content and any scroll y indicate that the
+                // translationY need to move up the HUN.
+                if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
+                    childState.yTranslation -= ambientState.getScrollY();
+                }
             }
             if (row.isHeadsUpAnimatingAway()) {
                 childState.hidden = false;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 4a9856b..2cbb78a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -351,11 +351,11 @@
     listItem.setOnSeekBarChangeListener(
         new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
     Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
-    primaryIcon.setTint(color);
+    primaryIcon.mutate().setTint(color);
     listItem.setPrimaryActionIcon(primaryIcon);
     if (supplementalIconId != 0) {
       Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
-      supplementalIcon.setTint(color);
+      supplementalIcon.mutate().setTint(color);
       listItem.setSupplementalIcon(supplementalIcon, true,
           supplementalIconOnClickListener);
     } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index d19715d..5ecf0c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -24,6 +24,7 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -66,6 +67,8 @@
     public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
     public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
     private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
+    private static final int OLD_BATTERY_LEVEL_NINE = 9;
+    private static final int OLD_BATTERY_LEVEL_10 = 10;
     private HardwarePropertiesManager mHardProps;
     private WarningsUI mMockWarnings;
     private PowerUI mPowerUI;
@@ -307,8 +310,8 @@
                 .thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true));
         mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
 
-        mPowerUI.maybeShowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                ABOVE_WARNING_BUCKET);
+        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
 
         // reduce battery level to handle time based trigger -> level trigger interactions
         mPowerUI.mBatteryLevel = 10;
@@ -449,6 +452,33 @@
         verify(mMockWarnings, never()).dismissLowBatteryWarning();
     }
 
+    @Test
+    public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
+        mPowerUI.start();
+        Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+        when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+        mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+
+        // we expect that the first time it will query even if the level is the same
+        mPowerUI.mBatteryLevel = 9;
+        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+        verify(mEnhancedEstimates, times(1)).getEstimate();
+
+        // We should NOT query again if the battery level hasn't changed
+        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+        verify(mEnhancedEstimates, times(1)).getEstimate();
+
+        // Battery level has changed, so we should query again
+        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
+                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+        verify(mEnhancedEstimates, times(2)).getEstimate();
+    }
+
     private void setCurrentTemp(float temp) {
         when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
                 .thenReturn(new float[] { temp });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
index 77522e4..f2f58938 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
@@ -38,11 +38,16 @@
 import android.Manifest;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.media.session.MediaSession;
 import android.os.Bundle;
 import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
@@ -72,6 +77,8 @@
     private static final int UID_ALLOW_DURING_SETUP = 456;
     private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
     private static final String TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY = "exempt";
+    private static final NotificationChannel NOTIFICATION_CHANNEL =
+            new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
 
     private final StatusBarNotification mMockStatusBarNotification =
             mock(StatusBarNotification.class);
@@ -145,11 +152,9 @@
     @Test
     public void testChannelSetWhenAdded() {
         mNotificationData.add(mRow.getEntry());
-        Assert.assertTrue(mRow.getEntry().channel != null);
+        assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel);
     }
 
-
-
     @Test
     public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
         mNotificationData.add(mRow.getEntry());
@@ -373,6 +378,32 @@
         assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
     }
 
+    @Test
+    public void testCreateNotificationDataEntry_RankingUpdate() {
+        Ranking ranking = mock(Ranking.class);
+
+        ArrayList<Notification.Action> smartActions = new ArrayList<>();
+        smartActions.add(createAction());
+        when(ranking.getSmartActions()).thenReturn(smartActions);
+
+        when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
+
+        when(ranking.getUserSentiment()).thenReturn(Ranking.USER_SENTIMENT_NEGATIVE);
+
+        SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation");
+        ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>();
+        snoozeCriterions.add(snoozeCriterion);
+        when(ranking.getSnoozeCriteria()).thenReturn(snoozeCriterions);
+
+        NotificationData.Entry entry =
+                new NotificationData.Entry(mMockStatusBarNotification, ranking);
+
+        assertEquals(smartActions, entry.smartActions);
+        assertEquals(NOTIFICATION_CHANNEL, entry.channel);
+        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
+        assertEquals(snoozeCriterions, entry.snoozeCriteria);
+    }
+
     private void initStatusBarNotification(boolean allowDuringSetup) {
         Bundle bundle = new Bundle();
         bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
@@ -388,12 +419,7 @@
         }
 
         @Override
-        public NotificationChannel getChannel(String key) {
-            return new NotificationChannel(null, null, 0);
-        }
-
-        @Override
-        protected boolean getRanking(String key, NotificationListenerService.Ranking outRanking) {
+        protected boolean getRanking(String key, Ranking outRanking) {
             super.getRanking(key, outRanking);
             if (key.equals(TEST_HIDDEN_NOTIFICATION_KEY)) {
                 outRanking.populate(key, outRanking.getRank(),
@@ -401,23 +427,31 @@
                         outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
                         outRanking.getImportance(), outRanking.getImportanceExplanation(),
                         outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
-                        outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+                        outRanking.canShowBadge(), outRanking.getUserSentiment(), true,
+                        null);
             } else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) {
                 outRanking.populate(key, outRanking.getRank(),
                         outRanking.matchesInterruptionFilter(),
                         outRanking.getVisibilityOverride(), 255,
                         outRanking.getImportance(), outRanking.getImportanceExplanation(),
                         outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
-                        outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+                        outRanking.canShowBadge(), outRanking.getUserSentiment(), true, null);
             } else {
                 outRanking.populate(key, outRanking.getRank(),
                         outRanking.matchesInterruptionFilter(),
                         outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
                         outRanking.getImportance(), outRanking.getImportanceExplanation(),
-                        outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
-                        outRanking.canShowBadge(), outRanking.getUserSentiment(), false);
+                        outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null,
+                        outRanking.canShowBadge(), outRanking.getUserSentiment(), false, null);
             }
             return true;
         }
     }
+
+    private Notification.Action createAction() {
+        return new Notification.Action.Builder(
+                Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+                "action",
+                PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
index afe16cf..e75e578 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -37,7 +37,10 @@
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -54,6 +57,8 @@
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -68,6 +73,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -99,6 +107,7 @@
     @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private SmartReplyController mSmartReplyController;
+    @Mock private RowInflaterTask mAsyncInflationTask;
 
     private NotificationData.Entry mEntry;
     private StatusBarNotification mSbn;
@@ -139,7 +148,26 @@
                     0,
                     NotificationManager.IMPORTANCE_DEFAULT,
                     null, null,
-                    null, null, null, true, sentiment, false);
+                    null, null, null, true, sentiment, false, null);
+            return true;
+        }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
+    }
+
+    private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) {
+        doAnswer(invocationOnMock -> {
+            NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
+                    invocationOnMock.getArguments()[1];
+            ranking.populate(
+                    key,
+                    0,
+                    false,
+                    0,
+                    0,
+                    NotificationManager.IMPORTANCE_DEFAULT,
+                    null, null,
+                    null, null, null, true,
+                    NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false,
+                    smartActions);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
     }
@@ -427,4 +455,71 @@
         Assert.assertTrue(newSbn.getNotification().extras
                 .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
     }
+
+    @Test
+    public void testUpdateNotificationRanking() {
+        when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+        when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+        mEntry.row = mRow;
+        mEntry.setInflationTask(mAsyncInflationTask);
+        mEntryManager.getNotificationData().add(mEntry);
+        setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+        mEntryManager.updateNotificationRanking(mRankingMap);
+        verify(mRow).updateNotification(eq(mEntry));
+        assertEquals(1, mEntry.smartActions.size());
+        assertEquals("action", mEntry.smartActions.get(0).title);
+    }
+
+    @Test
+    public void testUpdateNotificationRanking_noChange() {
+        when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+        when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+        mEntry.row = mRow;
+        mEntryManager.getNotificationData().add(mEntry);
+        setSmartActions(mEntry.key, null);
+
+        mEntryManager.updateNotificationRanking(mRankingMap);
+        verify(mRow, never()).updateNotification(eq(mEntry));
+        assertEquals(0, mEntry.smartActions.size());
+    }
+
+    @Test
+    public void testUpdateNotificationRanking_rowNotInflatedYet() {
+        when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+        when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+        mEntry.row = null;
+        mEntryManager.getNotificationData().add(mEntry);
+        setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+        mEntryManager.updateNotificationRanking(mRankingMap);
+        verify(mRow, never()).updateNotification(eq(mEntry));
+        assertEquals(1, mEntry.smartActions.size());
+        assertEquals("action", mEntry.smartActions.get(0).title);
+    }
+
+    @Test
+    public void testUpdateNotificationRanking_pendingNotification() {
+        when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+        when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+        mEntry.row = null;
+        mEntryManager.mPendingNotifications.put(mEntry.key, mEntry);
+        setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+        mEntryManager.updateNotificationRanking(mRankingMap);
+        verify(mRow, never()).updateNotification(eq(mEntry));
+        assertEquals(1, mEntry.smartActions.size());
+        assertEquals("action", mEntry.smartActions.get(0).title);
+    }
+
+    private Notification.Action createAction() {
+        return new Notification.Action.Builder(
+                Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+                "action",
+                PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
new file mode 100644
index 0000000..ce47e60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.support.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class NotificationUiAdjustmentTest extends SysuiTestCase {
+
+    @Test
+    public void needReinflate_differentLength() {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Notification.Action action =
+                createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.emptyList()),
+                new NotificationUiAdjustment("second", Collections.singletonList(action))))
+                .isTrue();
+    }
+
+    @Test
+    public void needReinflate_differentLabels() {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Notification.Action firstAction =
+                createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
+        Notification.Action secondAction =
+                createActionBuilder("second", R.drawable.ic_corp_icon, pendingIntent).build();
+
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+                new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+                .isTrue();
+    }
+
+    @Test
+    public void needReinflate_differentIcons() {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Notification.Action firstAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent).build();
+        Notification.Action secondAction =
+                createActionBuilder("same", R.drawable.ic_account_circle, pendingIntent)
+                        .build();
+
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+                new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+                .isTrue();
+    }
+
+    @Test
+    public void needReinflate_differentPendingIntent() {
+        PendingIntent firstPendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW), 0);
+        PendingIntent secondPendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_PROCESS_TEXT), 0);
+        Notification.Action firstAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, firstPendingIntent)
+                        .build();
+        Notification.Action secondAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, secondPendingIntent)
+                        .build();
+
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+                new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+                .isTrue();
+    }
+
+    @Test
+    public void needReinflate_differentChoices() {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+
+        RemoteInput firstRemoteInput =
+                createRemoteInput("same", "same", new CharSequence[] {"first"});
+        RemoteInput secondRemoteInput =
+                createRemoteInput("same", "same", new CharSequence[] {"second"});
+
+        Notification.Action firstAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+                        .addRemoteInput(firstRemoteInput)
+                        .build();
+        Notification.Action secondAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+                        .addRemoteInput(secondRemoteInput)
+                        .build();
+
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+                new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+                .isTrue();
+    }
+
+    @Test
+    public void needReinflate_differentRemoteInputLabel() {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+
+        RemoteInput firstRemoteInput =
+                createRemoteInput("same", "first", new CharSequence[] {"same"});
+        RemoteInput secondRemoteInput =
+                createRemoteInput("same", "second", new CharSequence[] {"same"});
+
+        Notification.Action firstAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+                        .addRemoteInput(firstRemoteInput)
+                        .build();
+        Notification.Action secondAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+                        .addRemoteInput(secondRemoteInput)
+                        .build();
+
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+                new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+                .isTrue();
+    }
+
+    @Test
+    public void needReinflate_negative() {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        RemoteInput firstRemoteInput =
+                createRemoteInput("same", "same", new CharSequence[] {"same"});
+        RemoteInput secondRemoteInput =
+                createRemoteInput("same", "same", new CharSequence[] {"same"});
+
+        Notification.Action firstAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+                        .addRemoteInput(firstRemoteInput).build();
+        Notification.Action secondAction =
+                createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+                        .addRemoteInput(secondRemoteInput).build();
+
+        assertThat(NotificationUiAdjustment.needReinflate(
+                new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+                new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+                .isFalse();
+    }
+
+    private Notification.Action.Builder createActionBuilder(
+            String title, int drawableRes, PendingIntent pendingIntent) {
+        return new Notification.Action.Builder(
+                Icon.createWithResource(mContext, drawableRes), title, pendingIntent);
+    }
+
+    private RemoteInput createRemoteInput(String resultKey, String label, CharSequence[] choices) {
+        return new RemoteInput.Builder(resultKey).setLabel(label).setChoices(choices).build();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 89d562a..9c55874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -152,6 +152,20 @@
     }
 
     @Test
+    public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.finishAnimationsImmediately();
+        mScrimController.setHasBackdrop(true);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible with tint
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimTint(mScrimBehind, true /* tinted */);
+        assertScrimTint(mScrimInFront, true /* tinted */);
+    }
+
+    @Test
     public void transitionToAod_withFrontAlphaUpdates() {
         // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
         mScrimController.transitionTo(ScrimState.KEYGUARD);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index dc6d16b..b6a5a9c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6160,6 +6160,14 @@
     // CATEGORY: SETTINGS
     // OS: Q
     FACE_ENROLL_FINISHED = 1508;
+
+    // OPEN: Face Enroll sidecar
+    // CATEGORY: SETTINGS
+    // OS: Q
+    FACE_ENROLL_SIDECAR = 1509;
+
+    // OPEN: Settings > Add face > Error dialog
+    DIALOG_FACE_ERROR = 5510;
     // ---- End Q Constants, all Q constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java b/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java
deleted file mode 100644
index 892c490..0000000
--- a/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java
+++ /dev/null
@@ -1,125 +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 android.sax;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.util.Xml;
-import org.kxml2.io.KXmlParser;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import com.android.frameworks.saxtests.R;
-
-public class ExpatPerformanceTest extends AndroidTestCase {
-
-    private static final String TAG = ExpatPerformanceTest.class.getSimpleName();
-
-    private byte[] mXmlBytes;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int length;
-        while ((length = in.read(buffer)) != -1) {
-            out.write(buffer, 0, length);
-        }
-        mXmlBytes = out.toByteArray();
-
-        Log.i("***", "File size: " + (mXmlBytes.length / 1024) + "k");
-    }
-
-    @LargeTest
-    public void testPerformance() throws Exception {
-//        try {
-//            Debug.startMethodTracing("expat3");
-//        for (int i = 0; i < 1; i++) {
-            runJavaPullParser();
-            runSax();
-            runExpatPullParser();
-//        }
-//    } finally {
-//            Debug.stopMethodTracing();
-//        }
-    }
-
-    private InputStream newInputStream() {
-        return new ByteArrayInputStream(mXmlBytes);
-    }
-
-    private void runSax() throws IOException, SAXException {
-        long start = System.currentTimeMillis();
-        Xml.parse(newInputStream(), Xml.Encoding.UTF_8, new DefaultHandler());
-        long elapsed = System.currentTimeMillis() - start;
-        Log.i(TAG, "expat SAX: " + elapsed + "ms");
-    }
-
-    private void runExpatPullParser() throws XmlPullParserException, IOException {
-        long start = System.currentTimeMillis();
-        XmlPullParser pullParser = Xml.newPullParser();
-        pullParser.setInput(newInputStream(), "UTF-8");
-        withPullParser(pullParser);
-        long elapsed = System.currentTimeMillis() - start;
-        Log.i(TAG, "expat pull: " + elapsed + "ms");
-    }
-
-    private void runJavaPullParser() throws XmlPullParserException, IOException {
-        XmlPullParser pullParser;
-        long start = System.currentTimeMillis();
-        pullParser = new KXmlParser();
-        pullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-        pullParser.setInput(newInputStream(), "UTF-8");
-        withPullParser(pullParser);
-        long elapsed = System.currentTimeMillis() - start;
-        Log.i(TAG, "java pull parser: " + elapsed + "ms");
-    }
-
-    private static void withPullParser(XmlPullParser pullParser)
-            throws IOException, XmlPullParserException {
-        int eventType = pullParser.next();
-        while (eventType != XmlPullParser.END_DOCUMENT) {
-            switch (eventType) {
-                case XmlPullParser.START_TAG:
-                    pullParser.getName();
-//                        int nattrs = pullParser.getAttributeCount();
-//                        for (int i = 0; i < nattrs; ++i) {
-//                            pullParser.getAttributeName(i);
-//                            pullParser.getAttributeValue(i);
-//                        }
-                    break;
-                case XmlPullParser.END_TAG:
-                    pullParser.getName();
-                    break;
-                case XmlPullParser.TEXT:
-                    pullParser.getText();
-                    break;
-            }
-            eventType = pullParser.next();
-        }
-    }
-}
diff --git a/services/Android.bp b/services/Android.bp
index d125adc..bea51be 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -2,6 +2,7 @@
 // ============================================================
 java_library {
     name: "services",
+    installable: true,
 
     dex_preopt: {
         app_image: true,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 41e9d2b..021fdcd 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -46,6 +46,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Parcelable;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -622,6 +623,38 @@
         return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings());
     }
 
+    private void send(@NonNull IResultReceiver receiver, int value) {
+        try {
+            receiver.send(value, null);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error async reporting result to client: " + e);
+        }
+    }
+
+    private void send(@NonNull IResultReceiver receiver, @NonNull Bundle value) {
+        try {
+            receiver.send(0, value);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error async reporting result to client: " + e);
+        }
+    }
+
+    private void send(@NonNull IResultReceiver receiver, @Nullable String value) {
+        send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+    }
+
+    private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) {
+        send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+    }
+
+    private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) {
+        send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+    }
+
+    private void send(@NonNull IResultReceiver receiver, boolean value) {
+        send(receiver, value ? 1 : 0);
+    }
+
     @Nullable
     @VisibleForTesting
     static Map<String, String[]> getWhitelistedCompatModePackages(String setting) {
@@ -827,9 +860,10 @@
 
     final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
         @Override
-        public int addClient(IAutoFillManagerClient client, int userId) {
+        public void addClient(IAutoFillManagerClient client, int userId,
+                @NonNull IResultReceiver receiver) {
+            int flags = 0;
             synchronized (mLock) {
-                int flags = 0;
                 if (getServiceForUserLocked(userId).addClientLocked(client)) {
                     flags |= AutofillManager.FLAG_ADD_CLIENT_ENABLED;
                 }
@@ -839,8 +873,8 @@
                 if (sVerbose) {
                     flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
                 }
-                return flags;
             }
+            send(receiver, flags);
         }
 
         @Override
@@ -874,9 +908,9 @@
         }
 
         @Override
-        public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
+        public void startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
                 Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
-                ComponentName componentName, boolean compatMode) {
+                ComponentName componentName, boolean compatMode, IResultReceiver receiver) {
 
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
             appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
@@ -892,61 +926,63 @@
                 throw new IllegalArgumentException(packageName + " is not a valid package", e);
             }
 
+            final int sessionId;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
-                return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
+                sessionId = service.startSessionLocked(activityToken, getCallingUid(), appCallback,
                         autofillId, bounds, value, hasCallback, componentName, compatMode,
                         mAllowInstantService, flags);
             }
+            send(receiver, sessionId);
         }
 
         @Override
-        public FillEventHistory getFillEventHistory() throws RemoteException {
+        public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
 
+            FillEventHistory fillEventHistory = null;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getFillEventHistory(getCallingUid());
+                    fillEventHistory = service.getFillEventHistory(getCallingUid());
                 } else if (sVerbose) {
                     Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
                 }
             }
-
-            return null;
+            send(receiver, fillEventHistory);
         }
 
         @Override
-        public UserData getUserData() throws RemoteException {
+        public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
 
+            UserData userData = null;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getUserData(getCallingUid());
+                    userData = service.getUserData(getCallingUid());
                 } else if (sVerbose) {
                     Slog.v(TAG, "getUserData(): no service for " + userId);
                 }
             }
-
-            return null;
+            send(receiver, userData);
         }
 
         @Override
-        public String getUserDataId() throws RemoteException {
+        public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
+            UserData userData = null;
 
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    final UserData userData = service.getUserData(getCallingUid());
-                    return userData == null ? null : userData.getId();
+                    userData = service.getUserData(getCallingUid());
                 } else if (sVerbose) {
                     Slog.v(TAG, "getUserDataId(): no service for " + userId);
                 }
             }
-
-            return null;
+            final String userDataId = userData == null ? null : userData.getId();
+            send(receiver, userDataId);
         }
 
         @Override
@@ -964,89 +1000,96 @@
         }
 
         @Override
-        public boolean isFieldClassificationEnabled() throws RemoteException {
+        public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver)
+                throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
+            boolean enabled = false;
 
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.isFieldClassificationEnabled(getCallingUid());
+                    enabled = service.isFieldClassificationEnabled(getCallingUid());
                 } else if (sVerbose) {
                     Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
                 }
             }
-
-            return false;
+            send(receiver, enabled);
         }
 
         @Override
-        public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+        public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver)
+                throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
+            String algorithm = null;
 
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+                    algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
                 } else {
                     if (sVerbose) {
                         Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
                     }
-                    return null;
                }
             }
+            send(receiver, algorithm);
         }
 
         @Override
-        public String[] getAvailableFieldClassificationAlgorithms() throws RemoteException {
+        public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver)
+                throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
+            String[] algorithms = null;
 
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+                    algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid());
                 } else {
                     if (sVerbose) {
                         Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
                     }
-                    return null;
                 }
             }
+            send(receiver, algorithms);
         }
 
         @Override
-        public ComponentName getAutofillServiceComponentName() throws RemoteException {
+        public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver)
+                throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
 
+            ComponentName componentName = null;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getServiceComponentName();
+                    componentName = service.getServiceComponentName();
                 } else if (sVerbose) {
                     Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
                 }
             }
-
-            return null;
+            send(receiver, componentName);
         }
 
         @Override
-        public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
+        public void restoreSession(int sessionId, @NonNull IBinder activityToken,
+                @NonNull IBinder appCallback, @NonNull IResultReceiver receiver)
                 throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
             appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
 
+            boolean restored = false;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = mServicesCache.get(userId);
                 if (service != null) {
-                    return service.restoreSession(sessionId, getCallingUid(), activityToken,
+                    restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
                             appCallback);
                 } else if (sVerbose) {
                     Slog.v(TAG, "restoreSession(): no service for " + userId);
                 }
             }
-
-            return false;
+            send(receiver, restored);
         }
 
         @Override
@@ -1112,23 +1155,27 @@
         }
 
         @Override
-        public boolean isServiceSupported(int userId) {
+        public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) {
+            boolean supported = false;
             synchronized (mLock) {
-                return !mDisabledUsers.get(userId);
+                supported = !mDisabledUsers.get(userId);
             }
+            send(receiver, supported);
         }
 
         @Override
-        public boolean isServiceEnabled(int userId, String packageName) {
+        public void isServiceEnabled(int userId, @NonNull String packageName,
+                @NonNull IResultReceiver receiver) {
+            boolean enabled = false;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return Objects.equals(packageName, service.getServicePackageName());
+                    enabled = Objects.equals(packageName, service.getServicePackageName());
                 } else if (sVerbose) {
                     Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
                 }
-                return false;
             }
+            send(receiver, enabled);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index de02e81..776cf47 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1549,6 +1549,22 @@
         return -1;
     }
 
+    private static String resolutionLevelToOpStr(int allowedResolutionLevel) {
+        switch(allowedResolutionLevel) {
+            case RESOLUTION_LEVEL_COARSE:
+                return AppOpsManager.OPSTR_COARSE_LOCATION;
+            case RESOLUTION_LEVEL_FINE:
+                return AppOpsManager.OPSTR_FINE_LOCATION;
+            case RESOLUTION_LEVEL_NONE:
+                // The client is not allowed to get any location, so both FINE and COARSE ops will
+                // be denied. Pick the most restrictive one to be safe.
+                return AppOpsManager.OPSTR_FINE_LOCATION;
+            default:
+                // Use the most restrictive ops if not sure.
+                return AppOpsManager.OPSTR_FINE_LOCATION;
+        }
+    }
+
     boolean reportLocationAccessNoThrow(
             int pid, int uid, String packageName, int allowedResolutionLevel) {
         int op = resolutionLevelToOp(allowedResolutionLevel);
@@ -2295,7 +2311,7 @@
                 }
 
                 // Don't return stale location to apps with foreground-only location permission.
-                String op = getResolutionPermission(allowedResolutionLevel);
+                String op = resolutionLevelToOpStr(allowedResolutionLevel);
                 long locationAgeMs = SystemClock.elapsedRealtime() -
                         location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
                 if ((locationAgeMs > mLastLocationMaxAgeMs)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b31c8db..14eeb78 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -520,6 +520,9 @@
     // Used to indicate that an app transition should be animated.
     static final boolean ANIMATE = true;
 
+    // If set, we will push process association information in to procstats.
+    static final boolean TRACK_PROCSTATS_ASSOCIATIONS = true;
+
     /**
      * Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}.
      */
@@ -563,6 +566,8 @@
 
     public final IntentFirewall mIntentFirewall;
 
+    public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
+
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
     private boolean mUseFifoUiScheduling = false;
 
@@ -2628,6 +2633,7 @@
         mOnBattery = DEBUG_POWER ? true
                 : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
         mBatteryStatsService.getActiveStatistics().setCallback(this);
+        mOomAdjProfiler.batteryPowerChanged(mOnBattery);
 
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
@@ -2916,12 +2922,14 @@
             synchronized(mPidsSelfLocked) {
                 mOnBattery = DEBUG_POWER ? true : onBattery;
             }
+            mOomAdjProfiler.batteryPowerChanged(onBattery);
         }
     }
 
     @Override
     public void batteryStatsReset() {
         BinderCallsStatsService.reset();
+        mOomAdjProfiler.reset();
     }
 
     @Override
@@ -10243,6 +10251,7 @@
                 mServices.updateScreenStateLocked(isAwake);
                 reportCurWakefulnessUsageEventLocked();
                 mActivityTaskManager.onScreenAwakeChanged(isAwake);
+                mOomAdjProfiler.onWakefulnessChanged(wakefulness);
             }
             updateOomAdjLocked();
         }
@@ -12113,7 +12122,7 @@
         return imp;
     }
 
-    private void fillInProcMemInfoLocked(ProcessRecord app,
+    private void fillInProcMemInfo(ProcessRecord app,
             ActivityManager.RunningAppProcessInfo outInfo,
             int clientTargetSdk) {
         outInfo.pid = app.pid;
@@ -12133,8 +12142,6 @@
         outInfo.importance = procStateToImportance(procState, adj, outInfo, clientTargetSdk);
         outInfo.importanceReasonCode = app.adjTypeCode;
         outInfo.processState = app.curProcState;
-        outInfo.isFocused = (app == getTopAppLocked());
-        outInfo.lastActivityTime = app.lastActivityTime;
     }
 
     @Override
@@ -12165,7 +12172,7 @@
                     ActivityManager.RunningAppProcessInfo currApp =
                         new ActivityManager.RunningAppProcessInfo(app.processName,
                                 app.pid, app.getPackageList());
-                    fillInProcMemInfoLocked(app, currApp, clientTargetSdk);
+                    fillInProcMemInfo(app, currApp, clientTargetSdk);
                     if (app.adjSource instanceof ProcessRecord) {
                         currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
                         currApp.importanceReasonImportance =
@@ -12234,7 +12241,7 @@
                 proc = mPidsSelfLocked.get(Binder.getCallingPid());
             }
             if (proc != null) {
-                fillInProcMemInfoLocked(proc, outState, clientTargetSdk);
+                fillInProcMemInfo(proc, outState, clientTargetSdk);
             }
         }
     }
@@ -12732,6 +12739,11 @@
                     pw.println("-------------------------------------------------------------------------------");
                 }
                 dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
+                mOomAdjProfiler.dump(pw);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -18880,6 +18892,7 @@
                 // The process is being computed, so there is a cycle. We cannot
                 // rely on this process's state.
                 app.containsCycle = true;
+
                 return false;
             }
         }
@@ -18904,6 +18917,7 @@
         final int logUid = mCurOomAdjUid;
 
         int prevAppAdj = app.curAdj;
+        int prevProcState = app.curProcState;
 
         if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
             // The max adjustment doesn't allow this app to be anything
@@ -19281,11 +19295,16 @@
                         ProcessRecord client = cr.binding.client;
                         computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
                         if (client.containsCycle) {
-                            // We've detected a cycle. We should ignore this connection and allow
-                            // this process to retry computeOomAdjLocked later in case a later-checked
-                            // connection from a client  would raise its priority legitimately.
+                            // We've detected a cycle. We should retry computeOomAdjLocked later in
+                            // case a later-checked connection from a client  would raise its
+                            // priority legitimately.
                             app.containsCycle = true;
-                            continue;
+                            // If the client has not been completely evaluated, skip using its
+                            // priority. Else use the conservative value for now and look for a
+                            // better state in the next iteration.
+                            if (client.completedAdjSeq < mAdjSeq) {
+                                continue;
+                            }
                         }
                         int clientAdj = client.curRawAdj;
                         int clientProcState = client.curProcState;
@@ -19514,11 +19533,16 @@
                 }
                 computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
                 if (client.containsCycle) {
-                    // We've detected a cycle. We should ignore this connection and allow
-                    // this process to retry computeOomAdjLocked later in case a later-checked
-                    // connection from a client  would raise its priority legitimately.
+                    // We've detected a cycle. We should retry computeOomAdjLocked later in
+                    // case a later-checked connection from a client  would raise its
+                    // priority legitimately.
                     app.containsCycle = true;
-                    continue;
+                    // If the client has not been completely evaluated, skip using its
+                    // priority. Else use the conservative value for now and look for a
+                    // better state in the next iteration.
+                    if (client.completedAdjSeq < mAdjSeq) {
+                        continue;
+                    }
                 }
                 int clientAdj = client.curRawAdj;
                 int clientProcState = client.curProcState;
@@ -19751,8 +19775,8 @@
         app.foregroundActivities = foregroundActivities;
         app.completedAdjSeq = mAdjSeq;
 
-        // if curAdj is less than prevAppAdj, then this process was promoted
-        return app.curAdj < prevAppAdj;
+        // if curAdj or curProcState improved, then this process was promoted
+        return app.curAdj < prevAppAdj || app.curProcState < prevProcState;
     }
 
     /**
@@ -20666,23 +20690,22 @@
     }
 
     @GuardedBy("this")
-    ProcessRecord getTopAppLocked() {
-        final ActivityRecord TOP_ACT = resumedAppLocked();
-        if (TOP_ACT != null && TOP_ACT.hasProcess()) {
-            return (ProcessRecord) TOP_ACT.app.mOwner;
-        } else {
-            return null;
-        }
-    }
-
-    @GuardedBy("this")
     final void updateOomAdjLocked() {
-        final ProcessRecord TOP_APP = getTopAppLocked();
+        mOomAdjProfiler.oomAdjStarted();
+        final ActivityRecord TOP_ACT = resumedAppLocked();
+        final ProcessRecord TOP_APP = TOP_ACT != null && TOP_ACT.hasProcess()
+                ? (ProcessRecord) TOP_ACT.app.mOwner : null;
         final long now = SystemClock.uptimeMillis();
         final long nowElapsed = SystemClock.elapsedRealtime();
         final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
         final int N = mLruProcesses.size();
 
+        if (false) {
+            RuntimeException e = new RuntimeException();
+            e.fillInStackTrace();
+            Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);
+        }
+
         // Reset state in all uid records.
         for (int i=mActiveUids.size()-1; i>=0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
@@ -20816,7 +20839,7 @@
         // - Continue retrying until no process was promoted.
         // - Iterate from least important to most important.
         int cycleCount = 0;
-        while (retryCycles) {
+        while (retryCycles && cycleCount < 10) {
             cycleCount++;
             retryCycles = false;
 
@@ -20831,6 +20854,7 @@
             for (int i=0; i<N; i++) {
                 ProcessRecord app = mLruProcesses.get(i);
                 if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+
                     if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now)) {
                         retryCycles = true;
                     }
@@ -21179,6 +21203,7 @@
                 Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
             }
         }
+        mOomAdjProfiler.oomAdjEnded();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index c22dedc..a1f1ff9 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -97,7 +97,7 @@
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
-            synchronized (this) {
+            synchronized (mService.mGlobalLock) {
                 mService.mStackSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId,
                         null);
             }
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 2cea4fa..fa8e6c4 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -108,10 +108,12 @@
     public void startAssociationIfNeeded() {
         // If we don't already have an active association, create one...  but only if this
         // is an association between two different processes.
-        if (association == null && (binding.service.appInfo.uid != clientUid
-                || !binding.service.processName.equals(clientProcessName))) {
-            ProcessStats.ProcessStateHolder holder = binding.service.app != null
-                    ? binding.service.app.pkgList.get(binding.service.name.getPackageName()) : null;
+        if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+                && association == null && binding.service.app != null
+                && (binding.service.appInfo.uid != clientUid
+                        || !binding.service.processName.equals(clientProcessName))) {
+            ProcessStats.ProcessStateHolder holder = binding.service.app.pkgList.get(
+                    binding.service.name.getPackageName());
             if (holder == null) {
                 Slog.wtf(TAG_AM, "No package in referenced service "
                         + binding.service.name.toShortString() + ": proc=" + binding.service.app);
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 36d3f4f..f2d4f73 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -55,10 +55,12 @@
     public void startAssociationIfNeeded() {
         // If we don't already have an active association, create one...  but only if this
         // is an association between two different processes.
-        if (association == null && (provider.appInfo.uid != client.uid
-                || !provider.info.processName.equals(client.processName))) {
-            ProcessStats.ProcessStateHolder holder = provider.proc != null
-                    ? provider.proc.pkgList.get(provider.name.getPackageName()) : null;
+        if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+                && association == null && provider.proc != null
+                && (provider.appInfo.uid != client.uid
+                        || !provider.info.processName.equals(client.processName))) {
+            ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+                    provider.name.getPackageName());
             if (holder == null) {
                 Slog.wtf(TAG_AM, "No package in referenced provider "
                         + provider.name.toShortString() + ": proc=" + provider.proc);
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 53783be..c22966d 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -90,21 +90,23 @@
 
     public void setProcess(ProcessRecord proc) {
         this.proc = proc;
-        for (int iconn = connections.size() - 1; iconn >= 0; iconn--) {
-            final ContentProviderConnection conn = connections.get(iconn);
-            if (proc != null) {
-                conn.startAssociationIfNeeded();
-            } else {
-                conn.stopAssociation();
-            }
-        }
-        if (externalProcessTokenToHandle != null) {
-            for (int iext = externalProcessTokenToHandle.size() - 1; iext >= 0; iext--) {
-                final ExternalProcessHandle handle = externalProcessTokenToHandle.valueAt(iext);
+        if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
+            for (int iconn = connections.size() - 1; iconn >= 0; iconn--) {
+                final ContentProviderConnection conn = connections.get(iconn);
                 if (proc != null) {
-                    handle.startAssociationIfNeeded(this);
+                    conn.startAssociationIfNeeded();
                 } else {
-                    handle.stopAssociation();
+                    conn.stopAssociation();
+                }
+            }
+            if (externalProcessTokenToHandle != null) {
+                for (int iext = externalProcessTokenToHandle.size() - 1; iext >= 0; iext--) {
+                    final ExternalProcessHandle handle = externalProcessTokenToHandle.valueAt(iext);
+                    if (proc != null) {
+                        handle.startAssociationIfNeeded(this);
+                    } else {
+                        handle.stopAssociation();
+                    }
                 }
             }
         }
@@ -287,10 +289,12 @@
         public void startAssociationIfNeeded(ContentProviderRecord provider) {
             // If we don't already have an active association, create one...  but only if this
             // is an association between two different processes.
-            if (mAssociation == null && (provider.appInfo.uid != mOwningUid
-                    || !provider.info.processName.equals(mOwningProcessName))) {
-                ProcessStats.ProcessStateHolder holder = provider.proc != null
-                        ? provider.proc.pkgList.get(provider.name.getPackageName()) : null;
+            if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+                    && mAssociation == null && provider.proc != null
+                    && (provider.appInfo.uid != mOwningUid
+                            || !provider.info.processName.equals(mOwningProcessName))) {
+                ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+                        provider.name.getPackageName());
                 if (holder == null) {
                     Slog.wtf(TAG_AM, "No package in referenced provider "
                             + provider.name.toShortString() + ": proc=" + provider.proc);
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
new file mode 100644
index 0000000..6230e0d
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjProfiler.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.PowerManagerInternal;
+import android.os.Process;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.util.RingBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.PrintWriter;
+
+public class OomAdjProfiler {
+    @GuardedBy("this")
+    private boolean mOnBattery;
+    @GuardedBy("this")
+    private boolean mScreenOff;
+
+    @GuardedBy("this")
+    private long mOomAdjStartTimeMs;
+    @GuardedBy("this")
+    private boolean mOomAdjStarted;
+
+    @GuardedBy("this")
+    private CpuTimes mOomAdjRunTime = new CpuTimes();
+    @GuardedBy("this")
+    private CpuTimes mSystemServerCpuTime = new CpuTimes();
+
+    @GuardedBy("this")
+    private long mLastSystemServerCpuTimeMs;
+    @GuardedBy("this")
+    private boolean mSystemServerCpuTimeUpdateScheduled;
+    private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
+
+    @GuardedBy("this")
+    final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
+    @GuardedBy("this")
+    final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
+
+    void batteryPowerChanged(boolean onBattery) {
+        synchronized (this) {
+            scheduleSystemServerCpuTimeUpdate();
+            mOnBattery = onBattery;
+        }
+    }
+
+    void onWakefulnessChanged(int wakefulness) {
+        synchronized (this) {
+            scheduleSystemServerCpuTimeUpdate();
+            mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
+        }
+    }
+
+    void oomAdjStarted() {
+        synchronized (this) {
+            mOomAdjStartTimeMs = SystemClock.currentThreadTimeMillis();
+            mOomAdjStarted = true;
+        }
+    }
+
+    void oomAdjEnded() {
+        synchronized (this) {
+            if (!mOomAdjStarted) {
+                return;
+            }
+            mOomAdjRunTime.addCpuTimeMs(SystemClock.currentThreadTimeMillis() - mOomAdjStartTimeMs);
+        }
+    }
+
+    private void scheduleSystemServerCpuTimeUpdate() {
+        synchronized (this) {
+            if (mSystemServerCpuTimeUpdateScheduled) {
+                return;
+            }
+            mSystemServerCpuTimeUpdateScheduled = true;
+            BackgroundThread.getHandler().post(PooledLambda.obtainRunnable(
+                    OomAdjProfiler::updateSystemServerCpuTime,
+                    this, mOnBattery, mScreenOff).recycleOnUse());
+        }
+    }
+
+    private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff) {
+        final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
+        synchronized (this) {
+            mSystemServerCpuTime.addCpuTimeMs(
+                    cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
+            mLastSystemServerCpuTimeMs = cpuTimeMs;
+            mSystemServerCpuTimeUpdateScheduled = false;
+            notifyAll();
+        }
+    }
+
+    void reset() {
+        synchronized (this) {
+            if (mSystemServerCpuTime.isEmpty()) {
+                return;
+            }
+            mOomAdjRunTimesHist.append(mOomAdjRunTime);
+            mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
+            mOomAdjRunTime = new CpuTimes();
+            mSystemServerCpuTime = new CpuTimes();
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        synchronized (this) {
+            if (mSystemServerCpuTimeUpdateScheduled) {
+                while (mSystemServerCpuTimeUpdateScheduled) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            } else {
+                updateSystemServerCpuTime(mOnBattery, mScreenOff);
+            }
+
+            pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
+                    + "(most recent first):");
+            if (!mSystemServerCpuTime.isEmpty()) {
+                pw.print("  ");
+                pw.print("system_server=");
+                pw.print(mSystemServerCpuTime);
+                pw.print("  ");
+                pw.print("oom_adj=");
+                pw.println(mOomAdjRunTime);
+            }
+            final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
+            final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
+            for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
+                pw.print("  ");
+                pw.print("system_server=");
+                pw.print(systemServerCpuTimes[i]);
+                pw.print("  ");
+                pw.print("oom_adj=");
+                pw.println(oomAdjRunTimes[i]);
+            }
+        }
+    }
+
+    private class CpuTimes {
+        private long mOnBatteryTimeMs;
+        private long mOnBatteryScreenOffTimeMs;
+
+        public void addCpuTimeMs(long cpuTimeMs) {
+            addCpuTimeMs(cpuTimeMs, mOnBattery, mScreenOff);
+        }
+
+        public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
+            if (onBattery) {
+                mOnBatteryTimeMs += cpuTimeMs;
+                if (screenOff) {
+                    mOnBatteryScreenOffTimeMs += cpuTimeMs;
+                }
+            }
+        }
+
+        public boolean isEmpty() {
+            return mOnBatteryTimeMs == 0 && mOnBatteryScreenOffTimeMs == 0;
+        }
+
+        public String toString() {
+            return "[" + mOnBatteryTimeMs + "," + mOnBatteryScreenOffTimeMs + "]";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index f72b801..8f43620 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -502,14 +502,16 @@
 
     public void setProcess(ProcessRecord _proc) {
         app = _proc;
-        for (int conni=connections.size()-1; conni>=0; conni--) {
-            ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
-            for (int i=0; i<cr.size(); i++) {
-                final ConnectionRecord conn = cr.get(i);
-                if (_proc != null) {
-                    conn.startAssociationIfNeeded();
-                } else {
-                    conn.stopAssociation();
+        if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
+            for (int conni = connections.size() - 1; conni >= 0; conni--) {
+                ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
+                for (int i = 0; i < cr.size(); i++) {
+                    final ConnectionRecord conn = cr.get(i);
+                    if (_proc != null) {
+                        conn.startAssociationIfNeeded();
+                    } else {
+                        conn.stopAssociation();
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
new file mode 100644
index 0000000..9e11eb0
--- /dev/null
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -0,0 +1,70 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsActivityManagerDeviceTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsActivityManagerDeviceSdk25TestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsAppTestCases",
+      "options": [
+        {
+          "include-filter": "android.app.cts.TaskDescriptionTest"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.am."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsActivityManagerDeviceTestCases"
+    },
+    {
+      "name": "CtsActivityManagerDeviceSdk25TestCases"
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.am."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 089632d..bfcc629 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -16,12 +16,10 @@
 
 package com.android.server.content;
 
+import android.annotation.Nullable;
 import android.app.job.JobParameters;
 import android.app.job.JobService;
-import android.content.Intent;
 import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
@@ -34,78 +32,86 @@
 public class SyncJobService extends JobService {
     private static final String TAG = "SyncManager";
 
-    public static final String EXTRA_MESSENGER = "messenger";
+    private static final Object sLock = new Object();
 
-    private Messenger mMessenger;
+    @GuardedBy("sLock")
+    private static SyncJobService sInstance;
 
-    private final Object mLock = new Object();
+    @GuardedBy("sLock")
+    private static final SparseArray<JobParameters> sJobParamsMap = new SparseArray<>();
 
-    @GuardedBy("mLock")
-    private final SparseArray<JobParameters> mJobParamsMap = new SparseArray<>();
+    @GuardedBy("sLock")
+    private static final SparseBooleanArray sStartedSyncs = new SparseBooleanArray();
 
-    @GuardedBy("mLock")
-    private final SparseBooleanArray mStartedSyncs = new SparseBooleanArray();
+    @GuardedBy("sLock")
+    private static final SparseLongArray sJobStartUptimes = new SparseLongArray();
 
-    @GuardedBy("mLock")
-    private final SparseLongArray mJobStartUptimes = new SparseLongArray();
+    private static final SyncLogger sLogger = SyncLogger.getInstance();
 
-    private final SyncLogger mLogger = SyncLogger.getInstance();
-
-    /**
-     * This service is started by the SyncManager which passes a messenger object to
-     * communicate back with it. It never stops while the device is running.
-     */
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        mMessenger = intent.getParcelableExtra(EXTRA_MESSENGER);
-        Message m = Message.obtain();
-        m.what = SyncManager.SyncHandler.MESSAGE_JOBSERVICE_OBJECT;
-        m.obj = this;
-        sendMessage(m);
-
-        return START_NOT_STICKY;
+    private void updateInstance() {
+        synchronized (SyncJobService.class) {
+            sInstance = this;
+        }
     }
 
-    private void sendMessage(Message message) {
-        if (mMessenger == null) {
-            Slog.e(TAG, "Messenger not initialized.");
-            return;
+    @Nullable
+    private static SyncJobService getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                Slog.wtf(TAG, "sInstance == null");
+            }
+            return sInstance;
         }
-        try {
-            mMessenger.send(message);
-        } catch (RemoteException e) {
-            Slog.e(TAG, e.toString());
+    }
+
+    public static boolean isReady() {
+        synchronized (sLock) {
+            return sInstance != null;
         }
     }
 
     @Override
     public boolean onStartJob(JobParameters params) {
+        updateInstance();
 
-        mLogger.purgeOldLogs();
+        sLogger.purgeOldLogs();
+
+        SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+
+        if (op == null) {
+            Slog.wtf(TAG, "Got invalid job " + params.getJobId());
+            return false;
+        }
+
+        final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
+
+        sLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op,
+                " readyToSync", readyToSync);
+
+        if (!readyToSync) {
+            // If the user isn't unlocked or the device has been provisioned yet, just stop the job
+            // at this point. If it's a non-periodic sync, ask the job scheduler to reschedule it.
+            // If it's a periodic sync, then just wait until the next cycle.
+            final boolean wantsReschedule = !op.isPeriodic;
+            jobFinished(params, wantsReschedule);
+            return true;
+        }
 
         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-        synchronized (mLock) {
+        synchronized (sLock) {
             final int jobId = params.getJobId();
-            mJobParamsMap.put(jobId, params);
+            sJobParamsMap.put(jobId, params);
 
-            mStartedSyncs.delete(jobId);
-            mJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
+            sStartedSyncs.delete(jobId);
+            sJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
-        SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
-
-        mLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op);
-
-        if (op == null) {
-            Slog.e(TAG, "Got invalid job " + params.getJobId());
-            return false;
-        }
         if (isLoggable) {
             Slog.v(TAG, "Got start job message " + op.target);
         }
         m.obj = op;
-        sendMessage(m);
+        SyncManager.sendMessage(m);
         return true;
     }
 
@@ -115,15 +121,22 @@
             Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
                     + params.getStopReason());
         }
-        final boolean readyToSync = SyncManager.readyToSync();
+        final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+        if (op == null) {
+            Slog.wtf(TAG, "Got invalid job " + params.getJobId());
+            return false;
+        }
 
-        mLogger.log("onStopJob() ", mLogger.jobParametersToString(params),
+        final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
+
+        sLogger.log("onStopJob() ", sLogger.jobParametersToString(params),
                 " readyToSync=", readyToSync);
-        synchronized (mLock) {
-            final int jobId = params.getJobId();
-            mJobParamsMap.remove(jobId);
 
-            final long startUptime = mJobStartUptimes.get(jobId);
+        synchronized (sLock) {
+            final int jobId = params.getJobId();
+            sJobParamsMap.remove(jobId);
+
+            final long startUptime = sJobStartUptimes.get(jobId);
             final long nowUptime = SystemClock.uptimeMillis();
             final long runtime = nowUptime - startUptime;
 
@@ -135,61 +148,57 @@
                 // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
                 // (1 minute threshold.)
                 // Also don't wtf when it's not ready to sync.
-                if (readyToSync && !mStartedSyncs.get(jobId)) {
+                if (readyToSync && !sStartedSyncs.get(jobId)) {
                     wtf("Job " + jobId + " didn't start: "
                             + " startUptime=" + startUptime
                             + " nowUptime=" + nowUptime
                             + " params=" + jobParametersToString(params));
                 }
-            } else if (runtime < 10 * 1000) {
-                // This happens too in a normal case too, and it's rather too often.
-                // Disable it for now.
-//                // Job stopped too soon. WTF.
-//                wtf("Job " + jobId + " stopped in " + runtime + " ms: "
-//                        + " startUptime=" + startUptime
-//                        + " nowUptime=" + nowUptime
-//                        + " params=" + jobParametersToString(params));
             }
 
-            mStartedSyncs.delete(jobId);
-            mJobStartUptimes.delete(jobId);
+            sStartedSyncs.delete(jobId);
+            sJobStartUptimes.delete(jobId);
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
-        m.obj = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
-        if (m.obj == null) {
-            return false;
-        }
+        m.obj = op;
 
         // Reschedule if this job was NOT explicitly canceled.
         m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
         // Apply backoff only if stop is called due to timeout.
         m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
 
-        sendMessage(m);
+        SyncManager.sendMessage(m);
         return false;
     }
 
-    public void callJobFinished(int jobId, boolean needsReschedule, String why) {
-        synchronized (mLock) {
-            JobParameters params = mJobParamsMap.get(jobId);
-            mLogger.log("callJobFinished()",
+    public static void callJobFinished(int jobId, boolean needsReschedule, String why) {
+        final SyncJobService instance = getInstance();
+        if (instance != null) {
+            instance.callJobFinishedInner(jobId, needsReschedule, why);
+        }
+    }
+
+    public void callJobFinishedInner(int jobId, boolean needsReschedule, String why) {
+        synchronized (sLock) {
+            JobParameters params = sJobParamsMap.get(jobId);
+            sLogger.log("callJobFinished()",
                     " jobid=", jobId,
                     " needsReschedule=", needsReschedule,
-                    " ", mLogger.jobParametersToString(params),
+                    " ", sLogger.jobParametersToString(params),
                     " why=", why);
             if (params != null) {
                 jobFinished(params, needsReschedule);
-                mJobParamsMap.remove(jobId);
+                sJobParamsMap.remove(jobId);
             } else {
                 Slog.e(TAG, "Job params not found for " + String.valueOf(jobId));
             }
         }
     }
 
-    public void markSyncStarted(int jobId) {
-        synchronized (mLock) {
-            mStartedSyncs.put(jobId, true);
+    public static void markSyncStarted(int jobId) {
+        synchronized (sLock) {
+            sStartedSyncs.put(jobId, true);
         }
     }
 
@@ -203,8 +212,8 @@
         }
     }
 
-    private void wtf(String message) {
-        mLogger.log(message);
+    private static void wtf(String message) {
+        sLogger.log(message);
         Slog.wtf(TAG, message);
     }
 }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 0a640b8..d06e785 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,6 +21,7 @@
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerInternal;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -72,7 +73,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteCallback;
@@ -89,6 +89,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -227,7 +228,6 @@
     // TODO: add better locking around mRunningAccounts
     private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
 
-    volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
     volatile private PowerManager.WakeLock mSyncManagerWakeLock;
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
@@ -238,7 +238,6 @@
     private final IBatteryStats mBatteryStats;
     private JobScheduler mJobScheduler;
     private JobSchedulerInternal mJobSchedulerInternal;
-    private SyncJobService mSyncJobService;
 
     private SyncStorageEngine mSyncStorageEngine;
 
@@ -318,16 +317,6 @@
                 }
             };
 
-    private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mBootCompleted = true;
-            // Called because it gets all pending jobs and stores them in mScheduledSyncs cache.
-            verifyJobScheduler();
-            mSyncHandler.onBootCompleted();
-        }
-    };
-
     private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -371,14 +360,14 @@
         m.sendToTarget();
     }
 
-    private void doDatabaseCleanup() {
+    private void removeStaleAccounts() {
         for (UserInfo user : mUserManager.getUsers(true)) {
             // Skip any partially created/removed users
             if (user.partial) continue;
             Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(
                     user.id, mContext.getOpPackageName());
 
-            mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
+            mSyncStorageEngine.removeStaleAccounts(accountsForUser, user.id);
         }
     }
 
@@ -464,8 +453,8 @@
     private final SyncHandler mSyncHandler;
     private final SyncManagerConstants mConstants;
 
-    private volatile boolean mBootCompleted = false;
-    private volatile boolean mJobServiceReady = false;
+    @GuardedBy("mUnlockedUsers")
+    private final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray();
 
     private ConnectivityManager getConnectivityManager() {
         synchronized (this) {
@@ -641,12 +630,6 @@
         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
 
-        if (!factoryTest) {
-            intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
-            intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-            context.registerReceiver(mBootCompletedReceiver, intentFilter);
-        }
-
         intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
         intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
         context.registerReceiver(mStorageIntentReceiver, intentFilter);
@@ -690,14 +673,6 @@
         mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
-        // This WakeLock is used to ensure that we stay awake between the time that we receive
-        // a sync alarm notification and when we finish processing it. We need to do this
-        // because we don't do the work in the alarm handler, rather we do it in a message
-        // handler.
-        mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                HANDLE_SYNC_ALARM_WAKE_LOCK);
-        mHandleAlarmWakeLock.setReferenceCounted(false);
-
         // This WakeLock is used to ensure that we stay awake while running the sync loop
         // message handler. Normally we will hold a sync adapter wake lock while it is being
         // synced but during the execution of the sync loop it might finish a sync for
@@ -715,7 +690,6 @@
                         public void onChange(boolean selfChange) {
                             mProvisioned |= isDeviceProvisioned();
                             if (mProvisioned) {
-                                mSyncHandler.onDeviceProvisioned();
                                 resolver.unregisterContentObserver(this);
                             }
                         }
@@ -744,19 +718,6 @@
                     null, null);
         }
 
-        // Set up the communication channel between the scheduled job and the sync manager.
-        // This is posted to the *main* looper intentionally, to defer calling startService()
-        // until after the lengthy primary boot sequence completes on that thread, to avoid
-        // spurious ANR triggering.
-        final Intent startServiceIntent = new Intent(mContext, SyncJobService.class);
-        startServiceIntent.putExtra(SyncJobService.EXTRA_MESSENGER, new Messenger(mSyncHandler));
-        new Handler(mContext.getMainLooper()).post(new Runnable() {
-            @Override
-            public void run() {
-                mContext.startService(startServiceIntent);
-            }
-        });
-
         // Sync adapters were able to access the synced account without the accounts
         // permission which circumvents our permission model. Therefore, we require
         // sync adapters that don't have access to the account to get user consent.
@@ -768,16 +729,31 @@
         mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
     }
 
-    public void onStartUser(int userHandle) {
-        mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userHandle));
+    public void onStartUser(int userId) {
+        // Log on the handler to avoid slowing down device boot.
+        mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userId));
     }
 
-    public void onUnlockUser(int userHandle) {
-        mSyncHandler.post(() -> mLogger.log("onUnlockUser: user=", userHandle));
+    public void onUnlockUser(int userId) {
+        synchronized (mUnlockedUsers) {
+            mUnlockedUsers.put(userId, true);
+        }
+        // Log on the handler to avoid slowing down device boot.
+        mSyncHandler.post(() -> mLogger.log("onUnlockUser: user=", userId));
     }
 
-    public void onStopUser(int userHandle) {
-        mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userHandle));
+    public void onStopUser(int userId) {
+        synchronized (mUnlockedUsers) {
+            mUnlockedUsers.put(userId, false);
+        }
+        // Log on the handler to avoid slowing down user switch.
+        mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userId));
+    }
+
+    private boolean isUserUnlocked(int userId) {
+        synchronized (mUnlockedUsers) {
+            return mUnlockedUsers.get(userId);
+        }
     }
 
     public void onBootPhase(int phase) {
@@ -1820,7 +1796,7 @@
         updateRunningAccounts(null /* Don't sync any target */);
 
         // Clean up the storage engine database
-        mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+        mSyncStorageEngine.removeStaleAccounts(null, userId);
         List<SyncOperation> ops = getAllPendingSyncs();
         for (SyncOperation op: ops) {
             if (op.target.userId == userId) {
@@ -2235,8 +2211,13 @@
         mSyncStorageEngine.resetTodayStats(/* force=*/ false);
 
         for (AccountAndUser account : accounts) {
-            pw.printf("Account %s u%d %s\n",
-                    account.account.name, account.userId, account.account.type);
+            final boolean unlocked;
+            synchronized (mUnlockedUsers) {
+                unlocked = mUnlockedUsers.get(account.userId);
+            }
+            pw.printf("Account %s u%d %s%s\n",
+                    account.account.name, account.userId, account.account.type,
+                    (unlocked ? "" : " (locked)"));
 
             pw.println("=======================================================================");
             final PrintTable table = new PrintTable(16);
@@ -2872,13 +2853,29 @@
         }
     }
 
-    /**
-     * @return whether the device is ready to run sync jobs.
-     */
-    public static boolean readyToSync() {
+    @Nullable
+    private static SyncManager getInstance() {
         synchronized (SyncManager.class) {
-            return sInstance != null && sInstance.mProvisioned && sInstance.mBootCompleted
-                    && sInstance.mJobServiceReady;
+            if (sInstance == null) {
+                Slog.wtf(TAG, "sInstance == null"); // Maybe called too early?
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * @return whether the device is ready to run sync jobs for a given user.
+     */
+    public static boolean readyToSync(int userId) {
+        final SyncManager instance = getInstance();
+        return (instance != null) && SyncJobService.isReady()
+                && instance.mProvisioned && instance.isUserUnlocked(userId);
+    }
+
+    public static void sendMessage(Message message) {
+        final SyncManager instance = getInstance();
+        if (instance != null) {
+            instance.mSyncHandler.sendMessage(message);
         }
     }
 
@@ -2889,11 +2886,9 @@
     class SyncHandler extends Handler {
         // Messages that can be sent on mHandler.
         private static final int MESSAGE_SYNC_FINISHED = 1;
-        private static final int MESSAGE_RELEASE_MESSAGES_FROM_QUEUE = 2;
         private static final int MESSAGE_SERVICE_CONNECTED = 4;
         private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
         private static final int MESSAGE_CANCEL = 6;
-        static final int MESSAGE_JOBSERVICE_OBJECT = 7;
         static final int MESSAGE_START_SYNC = 10;
         static final int MESSAGE_STOP_SYNC = 11;
         static final int MESSAGE_SCHEDULE_SYNC = 12;
@@ -2910,86 +2905,17 @@
         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
         private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap();
 
-        private List<Message> mUnreadyQueue = new ArrayList<Message>();
-
-        void onBootCompleted() {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Slog.v(TAG, "Boot completed.");
-            }
-            checkIfDeviceReady();
-        }
-
-        void onDeviceProvisioned() {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "mProvisioned=" + mProvisioned);
-            }
-            checkIfDeviceReady();
-        }
-
-        void checkIfDeviceReady() {
-            if (mProvisioned && mBootCompleted && mJobServiceReady) {
-                synchronized(this) {
-                    mSyncStorageEngine.restoreAllPeriodicSyncs();
-                    // Dispatch any stashed messages.
-                    obtainMessage(MESSAGE_RELEASE_MESSAGES_FROM_QUEUE).sendToTarget();
-                }
-            }
-        }
-
-        /**
-         * Stash any messages that come to the handler before boot is complete or before the device
-         * is properly provisioned (i.e. out of set-up wizard).
-         * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
-         * need to come in before we start syncing.
-         * @param msg Message to dispatch at a later point.
-         * @return true if a message was enqueued, false otherwise. This is to avoid losing the
-         * message if we manage to acquire the lock but by the time we do boot has completed.
-         */
-        private boolean tryEnqueueMessageUntilReadyToRun(Message msg) {
-            synchronized (this) {
-                if (!mBootCompleted || !mProvisioned || !mJobServiceReady) {
-                    // Need to copy the message bc looper will recycle it.
-                    Message m = Message.obtain(msg);
-                    mUnreadyQueue.add(m);
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-        }
-
         public SyncHandler(Looper looper) {
             super(looper);
         }
 
         public void handleMessage(Message msg) {
+            // TODO Do we really need this wake lock?? If we actually needed it, this is probably
+            // not the best place to acquire the lock -- it's probably too late, because the device
+            // could have gone to sleep before we reach here.
+            mSyncManagerWakeLock.acquire();
             try {
-                mSyncManagerWakeLock.acquire();
-                // We only want to enqueue sync related messages until device is ready.
-                // Other messages are handled without enqueuing.
-                if (msg.what == MESSAGE_JOBSERVICE_OBJECT) {
-                    Slog.i(TAG, "Got SyncJobService instance.");
-                    mSyncJobService = (SyncJobService) msg.obj;
-                    mJobServiceReady = true;
-                    checkIfDeviceReady();
-                } else if (msg.what == SyncHandler.MESSAGE_ACCOUNTS_UPDATED) {
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
-                    }
-                    EndPoint targets = (EndPoint) msg.obj;
-                    updateRunningAccountsH(targets);
-                } else if (msg.what == MESSAGE_RELEASE_MESSAGES_FROM_QUEUE) {
-                    if (mUnreadyQueue != null) {
-                        for (Message m : mUnreadyQueue) {
-                            handleSyncMessage(m);
-                        }
-                        mUnreadyQueue = null;
-                    }
-                } else if (tryEnqueueMessageUntilReadyToRun(msg)) {
-                    // No work to be done.
-                } else {
-                    handleSyncMessage(msg);
-                }
+                handleSyncMessage(msg);
             } finally {
                 mSyncManagerWakeLock.release();
             }
@@ -3001,6 +2927,13 @@
             try {
                 mDataConnectionIsConnected = readDataConnectionState();
                 switch (msg.what) {
+                    case MESSAGE_ACCOUNTS_UPDATED:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
+                        }
+                        EndPoint targets = (EndPoint) msg.obj;
+                        updateRunningAccountsH(targets);
+                        break;
                     case MESSAGE_SCHEDULE_SYNC:
                         ScheduleSyncMessagePayload syncPayload =
                                 (ScheduleSyncMessagePayload) msg.obj;
@@ -3069,7 +3002,7 @@
                         if (isLoggable) {
                             Slog.v(TAG, "syncFinished" + payload.activeSyncContext.mSyncOperation);
                         }
-                        mSyncJobService.callJobFinished(
+                        SyncJobService.callJobFinished(
                                 payload.activeSyncContext.mSyncOperation.jobId, false,
                                 "sync finished");
                         runSyncFinishedOrCanceledH(payload.syncResult,
@@ -3119,7 +3052,7 @@
                             // which is a soft error.
                             SyncResult syncResult = new SyncResult();
                             syncResult.stats.numIoExceptions++;
-                            mSyncJobService.callJobFinished(
+                            SyncJobService.callJobFinished(
                                     currentSyncContext.mSyncOperation.jobId, false,
                                     "service disconnected");
                             runSyncFinishedOrCanceledH(syncResult, currentSyncContext);
@@ -3138,7 +3071,7 @@
                             Log.w(TAG, String.format(
                                     "Detected sync making no progress for %s. cancelling.",
                                     monitoredSyncContext));
-                            mSyncJobService.callJobFinished(
+                            SyncJobService.callJobFinished(
                                     monitoredSyncContext.mSyncOperation.jobId, false,
                                     "no network activity");
                             runSyncFinishedOrCanceledH(
@@ -3175,7 +3108,7 @@
         private void deferSyncH(SyncOperation op, long delay, String why) {
             mLogger.log("deferSyncH() ", (op.isPeriodic ? "periodic " : ""),
                     "sync.  op=", op, " delay=", delay, " why=", why);
-            mSyncJobService.callJobFinished(op.jobId, false, why);
+            SyncJobService.callJobFinished(op.jobId, false, why);
             if (op.isPeriodic) {
                 scheduleSyncOperationH(op.createOneTimeSyncOperation(), delay);
             } else {
@@ -3213,7 +3146,7 @@
             // assume the clock is correct.
             mSyncStorageEngine.setClockValid();
 
-            mSyncJobService.markSyncStarted(op.jobId);
+            SyncJobService.markSyncStarted(op.jobId);
 
             if (mStorageIsLow) {
                 deferSyncH(op, SYNC_DELAY_ON_LOW_STORAGE, "storage low");
@@ -3226,7 +3159,7 @@
                 List<SyncOperation> ops = getAllPendingSyncs();
                 for (SyncOperation syncOperation: ops) {
                     if (syncOperation.sourcePeriodicId == op.jobId) {
-                        mSyncJobService.callJobFinished(op.jobId, false,
+                        SyncJobService.callJobFinished(op.jobId, false,
                                 "periodic sync, pending");
                         return;
                     }
@@ -3235,7 +3168,7 @@
                 // executing according to some backoff criteria.
                 for (ActiveSyncContext asc: mActiveSyncContexts) {
                     if (asc.mSyncOperation.sourcePeriodicId == op.jobId) {
-                        mSyncJobService.callJobFinished(op.jobId, false,
+                        SyncJobService.callJobFinished(op.jobId, false,
                                 "periodic sync, already running");
                         return;
                     }
@@ -3272,13 +3205,13 @@
             switch (syncOpState) {
                 case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS:
                 case SYNC_OP_STATE_INVALID: {
-                    mSyncJobService.callJobFinished(op.jobId, false,
+                    SyncJobService.callJobFinished(op.jobId, false,
                             "invalid op state: " + syncOpState);
                 } return;
             }
 
             if (!dispatchSyncOperation(op)) {
-                mSyncJobService.callJobFinished(op.jobId, false, "dispatchSyncOperation() failed");
+                SyncJobService.callJobFinished(op.jobId, false, "dispatchSyncOperation() failed");
             }
 
             setAuthorityPendingState(op.target);
@@ -3306,9 +3239,7 @@
             if (mLogger.enabled()) {
                 mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
             }
-            if (mBootCompleted) {
-                doDatabaseCleanup();
-            }
+            removeStaleAccounts();
 
             AccountAndUser[] accounts = mRunningAccounts;
             for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
@@ -3453,7 +3384,7 @@
                 if (op.sourcePeriodicId == syncOperation.jobId || op.jobId == syncOperation.jobId) {
                     ActiveSyncContext asc = findActiveSyncContextH(syncOperation.jobId);
                     if (asc != null) {
-                        mSyncJobService.callJobFinished(syncOperation.jobId, false,
+                        SyncJobService.callJobFinished(syncOperation.jobId, false,
                                 "removePeriodicSyncInternalH");
                         runSyncFinishedOrCanceledH(null, asc);
                     }
@@ -3662,7 +3593,7 @@
                                     false /* no config settings */)) {
                         continue;
                     }
-                    mSyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
+                    SyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
                             why);
                     runSyncFinishedOrCanceledH(null /* cancel => no result */, activeSyncContext);
                 }
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 811dc75..391e3b0 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -19,6 +19,7 @@
 import android.accounts.Account;
 import android.accounts.AccountAndUser;
 import android.accounts.AccountManager;
+import android.annotation.Nullable;
 import android.app.backup.BackupManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -1005,7 +1006,7 @@
      * Called when the set of account has changed, given the new array of
      * active accounts.
      */
-    public void doDatabaseCleanup(Account[] accounts, int userId) {
+    public void removeStaleAccounts(@Nullable Account[] accounts, int userId) {
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Slog.v(TAG, "Updating for new accounts...");
@@ -1014,8 +1015,9 @@
             Iterator<AccountInfo> accIt = mAccounts.values().iterator();
             while (accIt.hasNext()) {
                 AccountInfo acc = accIt.next();
-                if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
-                        && acc.accountAndUser.userId == userId) {
+                if ((accounts == null) || (
+                        (acc.accountAndUser.userId == userId)
+                        && !ArrayUtils.contains(accounts, acc.accountAndUser.account))) {
                     // This account no longer exists...
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Slog.v(TAG, "Account removed: " + acc.accountAndUser);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 99412c5..b124ac7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -791,9 +791,6 @@
                     && mAutomaticBrightnessController != null;
 
         final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
-        if (userSetBrightnessChanged) {
-            mTemporaryScreenBrightness = -1;
-        }
 
         // Use the temporary screen brightness if there isn't an override, either from
         // WindowManager or based on the display state.
@@ -1514,11 +1511,13 @@
         }
         if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
             mPendingScreenBrightnessSetting = -1;
+            mTemporaryScreenBrightness = -1;
             return false;
         }
         mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
         mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
         mPendingScreenBrightnessSetting = -1;
+        mTemporaryScreenBrightness = -1;
         return true;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index a2a55e532..0eb18a8 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -200,6 +200,8 @@
 
     static final int UNKNOWN_VOLUME = -1;
 
+    static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
+            "persist.sys.hdmi.addr.audiosystem";
     static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
     static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
 
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
index 2c1a7d5..15ec486 100644
--- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
@@ -85,7 +85,7 @@
 
     void processAllMessages() {
         // Use the copied buffer.
-        ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+        ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
         mBuffer.clear();
         for (HdmiCecMessage message : copiedBuffer) {
             mDevice.onMessage(message);
@@ -104,7 +104,7 @@
      *        are associated with
      */
     void processMessagesForDevice(int address) {
-        ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+        ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
         mBuffer.clear();
         HdmiLogger.debug("Checking message for address:" + address);
         for (HdmiCecMessage message : copiedBuffer) {
@@ -134,7 +134,7 @@
      * @param address logical address of the device to be the active source
      */
     void processActiveSource(int address) {
-        ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+        ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
         mBuffer.clear();
         for (HdmiCecMessage message : copiedBuffer) {
             if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index db8dedb..b75e75f 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -386,6 +386,7 @@
                 return;
             case STATE_WAITING_FOR_VENDOR_ID:
                 queryVendorId(address);
+                return;
             default:
                 return;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c7cdbef..1dd10f5 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -169,6 +169,8 @@
             return new HdmiCecLocalDeviceTv(service);
         case HdmiDeviceInfo.DEVICE_PLAYBACK:
             return new HdmiCecLocalDevicePlayback(service);
+        case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
+            return new HdmiCecLocalDeviceAudioSystem(service);
         default:
             return null;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
new file mode 100644
index 0000000..655166e
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 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.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioManager;
+import android.os.SystemProperties;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+/**
+ * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in
+ * Android system.
+ */
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+
+    private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
+
+    // Whether System audio mode is activated or not.
+    // This becomes true only when all system audio sequences are finished.
+    @GuardedBy("mLock")
+    private boolean mSystemAudioActivated;
+
+    protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
+        super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void onAddressAllocated(int logicalAddress, int reason) {
+        assertRunOnServiceThread();
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, mService.getPhysicalAddress(), mDeviceType));
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+                mAddress, mService.getVendorId()));
+        startQueuedActions();
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected int getPreferredAddress() {
+        assertRunOnServiceThread();
+        return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM,
+                Constants.ADDR_UNREGISTERED);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void setPreferredAddress(int addr) {
+        assertRunOnServiceThread();
+        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM,
+                String.valueOf(addr));
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // TODO(amyjojo): implement report audio status handler
+        HdmiLogger.debug(TAG + "Stub handleReportAudioStatus");
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleInitiateArc(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // TODO(amyjojo): implement initiate arc handler
+        HdmiLogger.debug(TAG + "Stub handleInitiateArc");
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleReportArcInitiate(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // TODO(amyjojo): implement report arc initiate handler
+        HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleReportArcTermination(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // TODO(amyjojo): implement report arc terminate handler
+        HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+
+        reportAudioStatus(message);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        mService.sendCecCommand(HdmiCecMessageBuilder
+            .buildReportSystemAudioMode(mAddress, message.getSource(), mSystemAudioActivated));
+        return true;
+    }
+
+    private void reportAudioStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+
+        int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
+        boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
+        int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+
+        mService.sendCecCommand(HdmiCecMessageBuilder
+            .buildReportAudioStatus(mAddress, message.getSource(), scaledVolume, mute));
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index fd0ff9d..c005615 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -16,9 +16,11 @@
 
 package com.android.server.hdmi;
 
+import android.annotation.Nullable;
 import libcore.util.EmptyArray;
 
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * A class to encapsulate HDMI-CEC message used for the devices connected via
@@ -44,6 +46,27 @@
         mParams = Arrays.copyOf(params, params.length);
     }
 
+    @Override
+    public boolean equals(@Nullable Object message) {
+        if (message instanceof HdmiCecMessage) {
+            HdmiCecMessage that = (HdmiCecMessage) message;
+            return this.mSource == that.getSource() &&
+                this.mDestination == that.getDestination() &&
+                this.mOpcode == that.getOpcode() &&
+                Arrays.equals(this.mParams, that.getParams());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+            mSource,
+            mDestination,
+            mOpcode,
+            Arrays.hashCode(mParams));
+    }
+
     /**
      * Return the source address field of the message. It is the logical address
      * of the device which generated the message.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 9a51e3c..37f96142 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -211,6 +211,28 @@
     }
 
     /**
+     * Build &lt;Initiate Arc&gt;
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildInitiateArc(int src, int dest) {
+        return buildCommand(src, dest, Constants.MESSAGE_INITIATE_ARC);
+    }
+
+    /**
+     * Build &lt;Terminate Arc&gt;
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildTerminateArc(int src, int dest) {
+        return buildCommand(src, dest, Constants.MESSAGE_TERMINATE_ARC);
+    }
+
+    /**
      * Build &lt;Request Arc Termination&gt;
      *
      * @param src source address of command
@@ -372,6 +394,34 @@
     }
 
     /**
+     * Build &lt;Set System Audio Mode&gt; command.
+     *
+     * @param src source address of command
+     * @param des destination address of command
+     * @param systemAudioStatus whether to set System Audio Mode on or off
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetSystemAudioMode(int src, int des, boolean systemAudioStatus) {
+        return buildCommandWithBooleanParam(src, des, Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE,
+            systemAudioStatus
+        );
+    }
+
+    /**
+     * Build &lt;Report System Audio Mode&gt; command.
+     *
+     * @param src source address of command
+     * @param des destination address of command
+     * @param systemAudioStatus whether System Audio Mode is on or off
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildReportSystemAudioMode(int src, int des, boolean systemAudioStatus) {
+        return buildCommandWithBooleanParam(src, des, Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
+            systemAudioStatus
+        );
+    }
+
+    /**
      * Build &lt;Give Audio Status&gt; command.
      *
      * @param src source address of command
@@ -383,6 +433,21 @@
     }
 
     /**
+     * Build &lt;Report Audio Status&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param volume volume level of current device in param
+     * @param mute mute status of current device in param
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) {
+        byte status = (byte) ((byte) (mute ? 1 << 7 : 0) | ((byte) volume & 0x7F));
+        byte[] params = new byte[] { status };
+        return buildCommand(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params);
+    }
+
+    /**
      * Build &lt;User Control Pressed&gt; command.
      *
      * @param src source address of command
@@ -592,6 +657,23 @@
         return new HdmiCecMessage(src, dest, opcode, params);
     }
 
+    /**
+     * Build a {@link HdmiCecMessage} with a boolean param and other given values.
+     *
+     * @param src source address of command
+     * @param des destination address of command
+     * @param opcode opcode for a message
+     * @param param boolean param for building the command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    private static HdmiCecMessage buildCommandWithBooleanParam(int src, int des,
+        int opcode, boolean param) {
+        byte[] params = new byte[]{
+            param ? (byte) 0b1 : 0b0
+        };
+        return buildCommand(src, des, opcode, params);
+    }
+
     private static byte[] physicalAddressToParam(int physicalAddress) {
         return new byte[] {
                 (byte) ((physicalAddress >> 8) & 0xFF),
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a1753e5..8a14639 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -96,7 +96,7 @@
 
     static final String PERMISSION = "android.permission.HDMI_CEC";
 
-    // The reason code to initiate intializeCec().
+    // The reason code to initiate initializeCec().
     static final int INITIATED_BY_ENABLE_CEC = 0;
     static final int INITIATED_BY_BOOT_UP = 1;
     static final int INITIATED_BY_SCREEN_ON = 2;
diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java
index ebe52c0..2309293 100644
--- a/services/core/java/com/android/server/hdmi/HdmiLogger.java
+++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java
@@ -17,7 +17,6 @@
 package com.android.server.hdmi;
 
 import android.annotation.Nullable;
-import android.os.Build;
 import android.os.SystemClock;
 import android.util.Pair;
 import android.util.Slog;
@@ -41,7 +40,7 @@
 final class HdmiLogger {
     private static final String TAG = "HDMI";
     // Logging duration for same error message.
-    private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000;  // 20s
+    private static final long ERROR_LOG_DURATION_MILLIS = 20 * 1000;  // 20s
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -134,6 +133,6 @@
     }
 
     private static boolean shouldLogNow(@Nullable Pair<Long, Integer> timing, long curTime) {
-        return timing == null || curTime - timing.first > ERROR_LOG_DURATTION_MILLIS;
+        return timing == null || curTime - timing.first > ERROR_LOG_DURATION_MILLIS;
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 4ac3bba..2a8117f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -114,7 +114,7 @@
      *
      * @param logicalAddress the logical address to verify
      * @param deviceType the device type to check
-     * @throw IllegalArgumentException
+     * @throws IllegalArgumentException
      */
     static void verifyAddressType(int logicalAddress, int deviceType) {
         int actualDeviceType = getTypeFromAddress(logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 6893012..a62d0b6 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -38,7 +38,7 @@
     private static final int INVALID_POWER_STATUS = POWER_STATUS_UNKNOWN - 1;
 
     // Monitoring interval (60s)
-    private static final int MONITIROING_INTERNAL_MS = 60000;
+    private static final int MONITORING_INTERNAL_MS = 60000;
 
     // Timeout once sending <Give Device Power Status>
     private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
@@ -132,7 +132,7 @@
         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
 
         // Add both timers, monitoring and timeout.
-        addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITIROING_INTERNAL_MS);
+        addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITORING_INTERNAL_MS);
         addTimer(STATE_WAIT_FOR_REPORT_POWER_STATUS, REPORT_POWER_STATUS_TIMEOUT_MS);
     }
 
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 75a79cb..c70101c 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -35,7 +35,7 @@
      *
      * @param source {@link HdmiCecLocalDevice} instance
      * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
-     * @throw IllegalArugmentException if device type of sourceAddress and avrAddress
+     * @throws IllegalArgumentException if device type of sourceAddress and avrAddress
      *                      is invalid
      */
     RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 449b208..a5477e8 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -60,7 +60,7 @@
      * @param avrAddress logical address of AVR device
      * @param targetStatus Whether to enable the system audio mode or not
      * @param callback callback interface to be notified when it's done
-     * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
+     * @throws IllegalArgumentException if device type of sourceAddress and avrAddress is invalid
      */
     SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus,
             IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index eb5119b..6ddff91 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -32,7 +32,7 @@
      * @param avrAddress logical address of AVR device
      * @param targetStatus Whether to enable the system audio mode or not
      * @param callback callback interface to be notified when it's done
-     * @throw IllegalArugmentException if device type of tvAddress and avrAddress is invalid
+     * @throws IllegalArgumentException if device type of tvAddress and avrAddress is invalid
      */
     SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress,
             boolean targetStatus, IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
index 02ecd13..5c0c272 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -32,7 +32,7 @@
      * @param avrAddress logical address of AVR device
      * @param targetStatus Whether to enable the system audio mode or not
      * @param callback callback interface to be notified when it's done
-     * @throw IllegalArugmentException if device type of tvAddress is invalid
+     * @throws IllegalArgumentException if device type of tvAddress is invalid
      */
     SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress,
             boolean targetStatus, IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index d41a36c..13f0f4ae 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -64,7 +64,7 @@
     }
 
     private void handleSendGiveAudioStatusFailure() {
-        // Inform to all application that the audio status (volumn, mute) of
+        // Inform to all application that the audio status (volume, mute) of
         // the audio amplifier is unknown.
         tv().setAudioStatus(false, Constants.UNKNOWN_VOLUME);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 71c7c88..dcdc203 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -386,6 +386,7 @@
     private static final String ATTR_VERSION = "version";
 
     private RankingHelper mRankingHelper;
+    private PreferencesHelper mPreferencesHelper;
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
@@ -525,8 +526,8 @@
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
                 mZenModeHelper.readXml(parser, forRestore);
-            } else if (RankingHelper.TAG_RANKING.equals(parser.getName())){
-                mRankingHelper.readXml(parser, forRestore);
+            } else if (PreferencesHelper.TAG_RANKING.equals(parser.getName())){
+                mPreferencesHelper.readXml(parser, forRestore);
             }
             if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
                 mListeners.readXml(parser, mAllowedManagedServicePackages);
@@ -608,7 +609,7 @@
         out.startTag(null, TAG_NOTIFICATION_POLICY);
         out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
         mZenModeHelper.writeXml(out, forBackup, null);
-        mRankingHelper.writeXml(out, forBackup);
+        mPreferencesHelper.writeXml(out, forBackup);
         mListeners.writeXml(out, forBackup);
         mAssistants.writeXml(out, forBackup);
         mConditionProviders.writeXml(out, forBackup);
@@ -949,7 +950,7 @@
                 // update system notification channels
                 SystemNotificationChannels.createAll(context);
                 mZenModeHelper.updateDefaultZenRules();
-                mRankingHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
+                mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
             }
         }
     };
@@ -1092,7 +1093,8 @@
                 mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
                 mAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
                 mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
-                mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
+                mPreferencesHelper.onPackagesChanged(
+                        removingPackage, changeUserId, pkgList, uidList);
                 savePolicyFile();
             }
         }
@@ -1152,7 +1154,7 @@
                 final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 mUserProfiles.updateCache(context);
                 mZenModeHelper.onUserRemoved(user);
-                mRankingHelper.onUserRemoved(user);
+                mPreferencesHelper.onUserRemoved(user);
                 mListeners.onUserRemoved(user);
                 mConditionProviders.onUserRemoved(user);
                 mAssistants.onUserRemoved(user);
@@ -1210,7 +1212,7 @@
                             Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, mMaxPackageEnqueueRate);
             }
             if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
-                mRankingHelper.updateBadgingEnabled();
+                mPreferencesHelper.updateBadgingEnabled();
             }
         }
     }
@@ -1332,6 +1334,9 @@
     }
 
     @VisibleForTesting
+    void setPreferencesHelper(PreferencesHelper prefHelper) { mPreferencesHelper = prefHelper; }
+
+    @VisibleForTesting
     void setRankingHandler(RankingHandler rankingHandler) {
         mRankingHandler = rankingHandler;
     }
@@ -1419,9 +1424,13 @@
                 mRankingHandler.requestSort();
             }
         });
-        mRankingHelper = new RankingHelper(getContext(),
+        mPreferencesHelper = new PreferencesHelper(getContext(),
                 mPackageManagerClient,
                 mRankingHandler,
+                mZenModeHelper);
+        mRankingHelper = new RankingHelper(getContext(),
+                mRankingHandler,
+                mPreferencesHelper,
                 mZenModeHelper,
                 mUsageStats,
                 extractorNames);
@@ -1676,14 +1685,14 @@
             }
         }
         final NotificationChannel preUpdate =
-                mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+                mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
 
-        mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+        mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true);
         maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
             final NotificationChannel modifiedChannel =
-                    mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                    mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
             mListeners.notifyNotificationChannelChanged(
                     pkg, UserHandle.getUserHandleForUid(uid),
                     modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -1720,8 +1729,8 @@
         Preconditions.checkNotNull(pkg);
 
         final NotificationChannelGroup preUpdate =
-                mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
-        mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
+                mPreferencesHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
+        mPreferencesHelper.createNotificationChannelGroup(pkg, uid, group,
                 fromApp);
         if (!fromApp) {
             maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
@@ -2124,7 +2133,7 @@
         public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
             enforceSystemOrSystemUI("setNotificationsEnabledForPackage");
 
-            mRankingHelper.setEnabled(pkg, uid, enabled);
+            mPreferencesHelper.setEnabled(pkg, uid, enabled);
             // Now, cancel any outstanding notifications that are part of a just-disabled app
             if (!enabled) {
                 cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
@@ -2162,7 +2171,7 @@
                 String pkg, int uid, boolean enabled) {
             setNotificationsEnabledForPackage(pkg, uid, enabled);
 
-            mRankingHelper.setAppImportanceLocked(pkg, uid);
+            mPreferencesHelper.setAppImportanceLocked(pkg, uid);
         }
 
         /**
@@ -2180,25 +2189,25 @@
         public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
             checkCallerIsSystemOrSameApp(pkg);
 
-            return mRankingHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
+            return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
         }
 
         @Override
         public int getPackageImportance(String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
-            return mRankingHelper.getImportance(pkg, Binder.getCallingUid());
+            return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
         }
 
         @Override
         public boolean canShowBadge(String pkg, int uid) {
             checkCallerIsSystem();
-            return mRankingHelper.canShowBadge(pkg, uid);
+            return mPreferencesHelper.canShowBadge(pkg, uid);
         }
 
         @Override
         public void setShowBadge(String pkg, int uid, boolean showBadge) {
             checkCallerIsSystem();
-            mRankingHelper.setShowBadge(pkg, uid, showBadge);
+            mPreferencesHelper.setShowBadge(pkg, uid, showBadge);
             savePolicyFile();
         }
 
@@ -2230,12 +2239,12 @@
             for (int i = 0; i < channelsSize; i++) {
                 final NotificationChannel channel = channels.get(i);
                 Preconditions.checkNotNull(channel, "channel in list is null");
-                mRankingHelper.createNotificationChannel(pkg, uid, channel,
+                mPreferencesHelper.createNotificationChannel(pkg, uid, channel,
                         true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
                                 pkg, UserHandle.getUserId(uid)));
                 mListeners.notifyNotificationChannelChanged(pkg,
                         UserHandle.getUserHandleForUid(uid),
-                        mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
+                        mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
                         NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
             }
             savePolicyFile();
@@ -2258,7 +2267,7 @@
         @Override
         public NotificationChannel getNotificationChannel(String pkg, String channelId) {
             checkCallerIsSystemOrSameApp(pkg);
-            return mRankingHelper.getNotificationChannel(
+            return mPreferencesHelper.getNotificationChannel(
                     pkg, Binder.getCallingUid(), channelId, false /* includeDeleted */);
         }
 
@@ -2266,7 +2275,7 @@
         public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
                 String channelId, boolean includeDeleted) {
             checkCallerIsSystem();
-            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
+            return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
         }
 
         @Override
@@ -2278,10 +2287,10 @@
             }
             cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
                     UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
-            mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId);
+            mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
             mListeners.notifyNotificationChannelChanged(pkg,
                     UserHandle.getUserHandleForUid(callingUid),
-                    mRankingHelper.getNotificationChannel(pkg, callingUid, channelId, true),
+                    mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true),
                     NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
             savePolicyFile();
         }
@@ -2289,7 +2298,7 @@
         @Override
         public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
             checkCallerIsSystemOrSameApp(pkg);
-            return mRankingHelper.getNotificationChannelGroupWithChannels(
+            return mPreferencesHelper.getNotificationChannelGroupWithChannels(
                     pkg, Binder.getCallingUid(), groupId, false);
         }
 
@@ -2297,7 +2306,7 @@
         public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
                 String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
-            return mRankingHelper.getNotificationChannelGroups(
+            return mPreferencesHelper.getNotificationChannelGroups(
                     pkg, Binder.getCallingUid(), false, false);
         }
 
@@ -2307,10 +2316,10 @@
 
             final int callingUid = Binder.getCallingUid();
             NotificationChannelGroup groupToDelete =
-                    mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
+                    mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
             if (groupToDelete != null) {
                 List<NotificationChannel> deletedChannels =
-                        mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
+                        mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
                 for (int i = 0; i < deletedChannels.size(); i++) {
                     final NotificationChannel deletedChannel = deletedChannels.get(i);
                     cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
@@ -2341,47 +2350,47 @@
         public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
                 int uid, boolean includeDeleted) {
             enforceSystemOrSystemUI("getNotificationChannelsForPackage");
-            return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted);
+            return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted);
         }
 
         @Override
         public int getNumNotificationChannelsForPackage(String pkg, int uid,
                 boolean includeDeleted) {
             enforceSystemOrSystemUI("getNumNotificationChannelsForPackage");
-            return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted)
+            return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted)
                     .getList().size();
         }
 
         @Override
         public boolean onlyHasDefaultChannel(String pkg, int uid) {
             enforceSystemOrSystemUI("onlyHasDefaultChannel");
-            return mRankingHelper.onlyHasDefaultChannel(pkg, uid);
+            return mPreferencesHelper.onlyHasDefaultChannel(pkg, uid);
         }
 
         @Override
         public int getDeletedChannelCount(String pkg, int uid) {
             enforceSystemOrSystemUI("getDeletedChannelCount");
-            return mRankingHelper.getDeletedChannelCount(pkg, uid);
+            return mPreferencesHelper.getDeletedChannelCount(pkg, uid);
         }
 
         @Override
         public int getBlockedChannelCount(String pkg, int uid) {
             enforceSystemOrSystemUI("getBlockedChannelCount");
-            return mRankingHelper.getBlockedChannelCount(pkg, uid);
+            return mPreferencesHelper.getBlockedChannelCount(pkg, uid);
         }
 
         @Override
         public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
                 String pkg, int uid, boolean includeDeleted) {
             checkCallerIsSystem();
-            return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
+            return mPreferencesHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
         }
 
         @Override
         public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(
                 String pkg, int uid, String groupId, boolean includeDeleted) {
             enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage");
-            return mRankingHelper.getNotificationChannelGroupWithChannels(
+            return mPreferencesHelper.getNotificationChannelGroupWithChannels(
                     pkg, uid, groupId, includeDeleted);
         }
 
@@ -2389,13 +2398,13 @@
         public NotificationChannelGroup getNotificationChannelGroupForPackage(
                 String groupId, String pkg, int uid) {
             enforceSystemOrSystemUI("getNotificationChannelGroupForPackage");
-            return mRankingHelper.getNotificationChannelGroup(groupId, pkg, uid);
+            return mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, uid);
         }
 
         @Override
         public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
-            return mRankingHelper.getNotificationChannels(
+            return mPreferencesHelper.getNotificationChannels(
                     pkg, Binder.getCallingUid(), false /* includeDeleted */);
         }
 
@@ -2412,12 +2421,12 @@
         @Override
         public int getBlockedAppCount(int userId) {
             checkCallerIsSystem();
-            return mRankingHelper.getBlockedAppCount(userId);
+            return mPreferencesHelper.getBlockedAppCount(userId);
         }
 
         @Override
         public boolean areChannelsBypassingDnd() {
-            return mRankingHelper.areChannelsBypassingDnd();
+            return mPreferencesHelper.areChannelsBypassingDnd();
         }
 
         @Override
@@ -2440,7 +2449,7 @@
 
             // Reset notification preferences
             if (!fromApp) {
-                mRankingHelper.onPackagesChanged(
+                mPreferencesHelper.onPackagesChanged(
                         true, UserHandle.getCallingUserId(), packages, uids);
             }
 
@@ -3512,7 +3521,7 @@
             Preconditions.checkNotNull(user);
             verifyPrivilegedListener(token, user);
 
-            return mRankingHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
+            return mPreferencesHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
                     false /* includeDeleted */);
         }
 
@@ -3525,7 +3534,7 @@
             verifyPrivilegedListener(token, user);
 
             List<NotificationChannelGroup> groups = new ArrayList<>();
-            groups.addAll(mRankingHelper.getNotificationChannelGroups(
+            groups.addAll(mPreferencesHelper.getNotificationChannelGroups(
                     pkg, getUidForPackageAndUser(pkg, user)));
             return new ParceledListSlice<>(groups);
         }
@@ -3706,10 +3715,10 @@
         JSONObject dump = new JSONObject();
         try {
             dump.put("service", "Notification Manager");
-            dump.put("bans", mRankingHelper.dumpBansJson(filter));
-            dump.put("ranking", mRankingHelper.dumpJson(filter));
+            dump.put("bans", mPreferencesHelper.dumpBansJson(filter));
+            dump.put("ranking", mPreferencesHelper.dumpJson(filter));
             dump.put("stats", mUsageStats.dumpJson(filter));
-            dump.put("channels", mRankingHelper.dumpChannelsJson(filter));
+            dump.put("channels", mPreferencesHelper.dumpChannelsJson(filter));
         } catch (JSONException e) {
             e.printStackTrace();
         }
@@ -3782,6 +3791,7 @@
 
             long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
             mRankingHelper.dump(proto, filter);
+            mPreferencesHelper.dump(proto, filter);
             proto.end(rankingToken);
         }
 
@@ -3890,6 +3900,9 @@
                 pw.println("\n  Ranking Config:");
                 mRankingHelper.dump(pw, "    ", filter);
 
+                pw.println("\n Notification Preferences:");
+                mPreferencesHelper.dump(pw, "    ", filter);
+
                 pw.println("\n  Notification listeners:");
                 mListeners.dump(pw, filter);
                 pw.print("    mListenerHints: "); pw.println(mListenerHints);
@@ -3953,7 +3966,7 @@
         @Override
         public NotificationChannel getNotificationChannel(String pkg, int uid, String
                 channelId) {
-            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+            return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, false);
         }
 
         @Override
@@ -4049,7 +4062,7 @@
         if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
-        final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
+        final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
                 notificationUid, channelId, false /* includeDeleted */);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
@@ -4064,7 +4077,7 @@
                     + ", notificationUid=" + notificationUid
                     + ", notification=" + notification;
             Log.e(TAG, noChannelStr);
-            boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
+            boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
                     == NotificationManager.IMPORTANCE_NONE;
 
             if (!appNotificationsOff) {
@@ -4079,7 +4092,7 @@
                 pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                 user, null, System.currentTimeMillis());
         final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
-        r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));
+        r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
 
         if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
             final boolean fgServiceShown = channel.isFgServiceShown();
@@ -4098,7 +4111,7 @@
                         channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
                         channel.setFgServiceShown(true);
                     }
-                    mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+                    mPreferencesHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
                     r.updateNotificationChannel(channel);
                 }
             } else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
@@ -4273,8 +4286,8 @@
             return isPackageSuspended;
         }
         final boolean isBlocked =
-                mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
-                || mRankingHelper.getImportance(pkg, callingUid)
+                mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
+                || mPreferencesHelper.getImportance(pkg, callingUid)
                         == NotificationManager.IMPORTANCE_NONE
                 || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
         if (isBlocked) {
@@ -5247,6 +5260,7 @@
             ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
             ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
             ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
+            ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
                 orderBefore.add(r.getKey());
@@ -5258,6 +5272,7 @@
                 snoozeCriteriaBefore.add(r.getSnoozeCriteria());
                 userSentimentBefore.add(r.getUserSentiment());
                 suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
+                smartActionsBefore.add(r.getSmartActions());
                 mRankingHelper.extractSignals(r);
             }
             mRankingHelper.sort(mNotificationList);
@@ -5272,7 +5287,8 @@
                         || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
                         || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
                         || !Objects.equals(suppressVisuallyBefore.get(i),
-                        r.getSuppressedVisualEffects())) {
+                        r.getSuppressedVisualEffects())
+                        || !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())) {
                     mHandler.scheduleSendRankingUpdate();
                     return;
                 }
@@ -6255,6 +6271,7 @@
         Bundle showBadge = new Bundle();
         Bundle userSentiment = new Bundle();
         Bundle hidden = new Bundle();
+        Bundle smartActions = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -6282,6 +6299,7 @@
             showBadge.putBoolean(key, record.canShowBadge());
             userSentiment.putInt(key, record.getUserSentiment());
             hidden.putBoolean(key, record.isHidden());
+            smartActions.putParcelableArrayList(key, record.getSmartActions());
         }
         final int M = keys.size();
         String[] keysAr = keys.toArray(new String[M]);
@@ -6292,7 +6310,8 @@
         }
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden);
+                channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
+                smartActions);
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1ab05ec..0154c72 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -159,6 +159,7 @@
     private Light mLight;
     private String mGroupLogTag;
     private String mChannelIdLogTag;
+    private ArrayList<Notification.Action> mSmartActions;
 
     private final List<Adjustment> mAdjustments;
     private final NotificationStats mStats;
@@ -630,6 +631,9 @@
                                 Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
                     }
                 }
+                if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
+                    setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+                }
             }
         }
     }
@@ -1049,6 +1053,14 @@
         mHasSeenSmartReplies = hasSeenSmartReplies;
     }
 
+    public void setSmartActions(ArrayList<Notification.Action> smartActions) {
+        mSmartActions = smartActions;
+    }
+
+    public ArrayList<Notification.Action> getSmartActions() {
+        return mSmartActions;
+    }
+
     /**
      * @return all {@link Uri} that should have permission granted to whoever
      *         will be rendering it. This list has already been vetted to only
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
new file mode 100644
index 0000000..dfc61d9
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -0,0 +1,1373 @@
+/**
+ * Copyright (c) 2018, 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.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.metrics.LogMaker;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.RankingHelperProto;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PreferencesHelper implements RankingConfig {
+    private static final String TAG = "NotificationPrefHelper";
+    private static final int XML_VERSION = 1;
+
+    @VisibleForTesting
+    static final String TAG_RANKING = "ranking";
+    private static final String TAG_PACKAGE = "package";
+    private static final String TAG_CHANNEL = "channel";
+    private static final String TAG_GROUP = "channelGroup";
+
+    private static final String ATT_VERSION = "version";
+    private static final String ATT_NAME = "name";
+    private static final String ATT_UID = "uid";
+    private static final String ATT_ID = "id";
+    private static final String ATT_PRIORITY = "priority";
+    private static final String ATT_VISIBILITY = "visibility";
+    private static final String ATT_IMPORTANCE = "importance";
+    private static final String ATT_SHOW_BADGE = "show_badge";
+    private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
+
+    private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
+    private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
+    private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
+    private static final boolean DEFAULT_SHOW_BADGE = true;
+    /**
+     * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
+     * fields.
+     */
+    private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
+
+    /**
+     * All user-lockable fields for a given application.
+     */
+    @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
+    public @interface LockableAppFields {
+        int USER_LOCKED_IMPORTANCE = 0x00000001;
+    }
+
+    // pkg|uid => PackagePreferences
+    private final ArrayMap<String, PackagePreferences> mPackagePreferencess = new ArrayMap<>();
+    // pkg => PackagePreferences
+    private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
+
+
+    private final Context mContext;
+    private final PackageManager mPm;
+    private final RankingHandler mRankingHandler;
+    private final ZenModeHelper mZenModeHelper;
+
+    private SparseBooleanArray mBadgingEnabled;
+    private boolean mAreChannelsBypassingDnd;
+
+
+    public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+            ZenModeHelper zenHelper) {
+        mContext = context;
+        mZenModeHelper = zenHelper;
+        mRankingHandler = rankingHandler;
+        mPm = pm;
+
+        updateBadgingEnabled();
+
+        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+        updateChannelsBypassingDnd();
+
+    }
+
+    public void readXml(XmlPullParser parser, boolean forRestore)
+            throws XmlPullParserException, IOException {
+        int type = parser.getEventType();
+        if (type != XmlPullParser.START_TAG) return;
+        String tag = parser.getName();
+        if (!TAG_RANKING.equals(tag)) return;
+        // Clobber groups and channels with the xml, but don't delete other data that wasn't present
+        // at the time of serialization.
+        mRestoredWithoutUids.clear();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            tag = parser.getName();
+            if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
+                return;
+            }
+            if (type == XmlPullParser.START_TAG) {
+                if (TAG_PACKAGE.equals(tag)) {
+                    int uid = XmlUtils.readIntAttribute(parser, ATT_UID,
+                            PackagePreferences.UNKNOWN_UID);
+                    String name = parser.getAttributeValue(null, ATT_NAME);
+                    if (!TextUtils.isEmpty(name)) {
+                        if (forRestore) {
+                            try {
+                                //TODO: http://b/22388012
+                                uid = mPm.getPackageUidAsUser(name,
+                                        UserHandle.USER_SYSTEM);
+                            } catch (PackageManager.NameNotFoundException e) {
+                                // noop
+                            }
+                        }
+
+                        PackagePreferences r = getOrCreatePackagePreferences(name, uid,
+                                XmlUtils.readIntAttribute(
+                                        parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
+                                XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
+                                XmlUtils.readIntAttribute(
+                                        parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+                                XmlUtils.readBooleanAttribute(
+                                        parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
+                        r.importance = XmlUtils.readIntAttribute(
+                                parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+                        r.priority = XmlUtils.readIntAttribute(
+                                parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+                        r.visibility = XmlUtils.readIntAttribute(
+                                parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+                        r.showBadge = XmlUtils.readBooleanAttribute(
+                                parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
+                        r.lockedAppFields = XmlUtils.readIntAttribute(parser,
+                                ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
+
+                        final int innerDepth = parser.getDepth();
+                        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                                && (type != XmlPullParser.END_TAG
+                                || parser.getDepth() > innerDepth)) {
+                            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                                continue;
+                            }
+
+                            String tagName = parser.getName();
+                            // Channel groups
+                            if (TAG_GROUP.equals(tagName)) {
+                                String id = parser.getAttributeValue(null, ATT_ID);
+                                CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+                                if (!TextUtils.isEmpty(id)) {
+                                    NotificationChannelGroup group
+                                            = new NotificationChannelGroup(id, groupName);
+                                    group.populateFromXml(parser);
+                                    r.groups.put(id, group);
+                                }
+                            }
+                            // Channels
+                            if (TAG_CHANNEL.equals(tagName)) {
+                                String id = parser.getAttributeValue(null, ATT_ID);
+                                String channelName = parser.getAttributeValue(null, ATT_NAME);
+                                int channelImportance = XmlUtils.readIntAttribute(
+                                        parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+                                if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
+                                    NotificationChannel channel = new NotificationChannel(id,
+                                            channelName, channelImportance);
+                                    if (forRestore) {
+                                        channel.populateFromXmlForRestore(parser, mContext);
+                                    } else {
+                                        channel.populateFromXml(parser);
+                                    }
+                                    r.channels.put(id, channel);
+                                }
+                            }
+                        }
+
+                        try {
+                            deleteDefaultChannelIfNeeded(r);
+                        } catch (PackageManager.NameNotFoundException e) {
+                            Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+                        }
+                    }
+                }
+            }
+        }
+        throw new IllegalStateException("Failed to reach END_DOCUMENT");
+    }
+
+    private PackagePreferences getPackagePreferences(String pkg, int uid) {
+        final String key = packagePreferencesKey(pkg, uid);
+        synchronized (mPackagePreferencess) {
+            return mPackagePreferencess.get(key);
+        }
+    }
+
+    private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
+        return getOrCreatePackagePreferences(pkg, uid,
+                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
+    }
+
+    private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
+            int priority, int visibility, boolean showBadge) {
+        final String key = packagePreferencesKey(pkg, uid);
+        synchronized (mPackagePreferencess) {
+            PackagePreferences
+                    r = (uid == PackagePreferences.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+                    : mPackagePreferencess.get(key);
+            if (r == null) {
+                r = new PackagePreferences();
+                r.pkg = pkg;
+                r.uid = uid;
+                r.importance = importance;
+                r.priority = priority;
+                r.visibility = visibility;
+                r.showBadge = showBadge;
+
+                try {
+                    createDefaultChannelIfNeeded(r);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+                }
+
+                if (r.uid == PackagePreferences.UNKNOWN_UID) {
+                    mRestoredWithoutUids.put(pkg, r);
+                } else {
+                    mPackagePreferencess.put(key, r);
+                }
+            }
+            return r;
+        }
+    }
+
+    private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
+            PackageManager.NameNotFoundException {
+        final int userId = UserHandle.getUserId(r.uid);
+        final ApplicationInfo applicationInfo =
+                mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+        if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
+            // O apps should not have the default channel.
+            return false;
+        }
+
+        // Otherwise, this app should have the default channel.
+        return true;
+    }
+
+    private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
+            PackageManager.NameNotFoundException {
+        if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            // Not present
+            return;
+        }
+
+        if (shouldHaveDefaultChannel(r)) {
+            // Keep the default channel until upgraded.
+            return;
+        }
+
+        // Remove Default Channel.
+        r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+    }
+
+    private void createDefaultChannelIfNeeded(PackagePreferences r) throws
+            PackageManager.NameNotFoundException {
+        if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
+                    com.android.internal.R.string.default_notification_channel_label));
+            return;
+        }
+
+        if (!shouldHaveDefaultChannel(r)) {
+            // Keep the default channel until upgraded.
+            return;
+        }
+
+        // Create Default Channel
+        NotificationChannel channel;
+        channel = new NotificationChannel(
+                NotificationChannel.DEFAULT_CHANNEL_ID,
+                mContext.getString(R.string.default_notification_channel_label),
+                r.importance);
+        channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+        channel.setLockscreenVisibility(r.visibility);
+        if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+        }
+        if (r.priority != DEFAULT_PRIORITY) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        }
+        if (r.visibility != DEFAULT_VISIBILITY) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+        }
+        r.channels.put(channel.getId(), channel);
+    }
+
+    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
+        out.startTag(null, TAG_RANKING);
+        out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
+
+        synchronized (mPackagePreferencess) {
+            final int N = mPackagePreferencess.size();
+            for (int i = 0; i < N; i++) {
+                final PackagePreferences r = mPackagePreferencess.valueAt(i);
+                //TODO: http://b/22388012
+                if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
+                    continue;
+                }
+                final boolean hasNonDefaultSettings =
+                        r.importance != DEFAULT_IMPORTANCE
+                                || r.priority != DEFAULT_PRIORITY
+                                || r.visibility != DEFAULT_VISIBILITY
+                                || r.showBadge != DEFAULT_SHOW_BADGE
+                                || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
+                                || r.channels.size() > 0
+                                || r.groups.size() > 0;
+                if (hasNonDefaultSettings) {
+                    out.startTag(null, TAG_PACKAGE);
+                    out.attribute(null, ATT_NAME, r.pkg);
+                    if (r.importance != DEFAULT_IMPORTANCE) {
+                        out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
+                    }
+                    if (r.priority != DEFAULT_PRIORITY) {
+                        out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
+                    }
+                    if (r.visibility != DEFAULT_VISIBILITY) {
+                        out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
+                    }
+                    out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
+                    out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
+                            Integer.toString(r.lockedAppFields));
+
+                    if (!forBackup) {
+                        out.attribute(null, ATT_UID, Integer.toString(r.uid));
+                    }
+
+                    for (NotificationChannelGroup group : r.groups.values()) {
+                        group.writeXml(out);
+                    }
+
+                    for (NotificationChannel channel : r.channels.values()) {
+                        if (forBackup) {
+                            if (!channel.isDeleted()) {
+                                channel.writeXmlForBackup(out, mContext);
+                            }
+                        } else {
+                            channel.writeXml(out);
+                        }
+                    }
+
+                    out.endTag(null, TAG_PACKAGE);
+                }
+            }
+        }
+        out.endTag(null, TAG_RANKING);
+    }
+
+    /**
+     * Gets importance.
+     */
+    @Override
+    public int getImportance(String packageName, int uid) {
+        return getOrCreatePackagePreferences(packageName, uid).importance;
+    }
+
+
+    /**
+     * Returns whether the importance of the corresponding notification is user-locked and shouldn't
+     * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
+     * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
+     */
+    public boolean getIsAppImportanceLocked(String packageName, int uid) {
+        int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
+        return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
+    }
+
+    @Override
+    public boolean canShowBadge(String packageName, int uid) {
+        return getOrCreatePackagePreferences(packageName, uid).showBadge;
+    }
+
+    @Override
+    public void setShowBadge(String packageName, int uid, boolean showBadge) {
+        getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
+        updateConfig();
+    }
+
+    @Override
+    public boolean isGroupBlocked(String packageName, int uid, String groupId) {
+        if (groupId == null) {
+            return false;
+        }
+        PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
+        NotificationChannelGroup group = r.groups.get(groupId);
+        if (group == null) {
+            return false;
+        }
+        return group.isBlocked();
+    }
+
+    int getPackagePriority(String pkg, int uid) {
+        return getOrCreatePackagePreferences(pkg, uid).priority;
+    }
+
+    int getPackageVisibility(String pkg, int uid) {
+        return getOrCreatePackagePreferences(pkg, uid).visibility;
+    }
+
+    @Override
+    public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+            boolean fromTargetApp) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(group);
+        Preconditions.checkNotNull(group.getId());
+        Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
+        PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+        if (r == null) {
+            throw new IllegalArgumentException("Invalid package");
+        }
+        final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
+        if (!group.equals(oldGroup)) {
+            // will log for new entries as well as name/description changes
+            MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
+        }
+        if (oldGroup != null) {
+            group.setChannels(oldGroup.getChannels());
+
+            if (fromTargetApp) {
+                group.setBlocked(oldGroup.isBlocked());
+            }
+        }
+        r.groups.put(group.getId(), group);
+    }
+
+    @Override
+    public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+            boolean fromTargetApp, boolean hasDndAccess) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(channel);
+        Preconditions.checkNotNull(channel.getId());
+        Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
+        PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+        if (r == null) {
+            throw new IllegalArgumentException("Invalid package");
+        }
+        if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
+            throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
+        }
+        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
+            throw new IllegalArgumentException("Reserved id");
+        }
+        NotificationChannel existing = r.channels.get(channel.getId());
+        // Keep most of the existing settings
+        if (existing != null && fromTargetApp) {
+            if (existing.isDeleted()) {
+                existing.setDeleted(false);
+
+                // log a resurrected channel as if it's new again
+                MetricsLogger.action(getChannelLog(channel, pkg).setType(
+                        com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+            }
+
+            existing.setName(channel.getName().toString());
+            existing.setDescription(channel.getDescription());
+            existing.setBlockableSystem(channel.isBlockableSystem());
+            if (existing.getGroup() == null) {
+                existing.setGroup(channel.getGroup());
+            }
+
+            // Apps are allowed to downgrade channel importance if the user has not changed any
+            // fields on this channel yet.
+            if (existing.getUserLockedFields() == 0 &&
+                    channel.getImportance() < existing.getImportance()) {
+                existing.setImportance(channel.getImportance());
+            }
+
+            // system apps and dnd access apps can bypass dnd if the user hasn't changed any
+            // fields on the channel yet
+            if (existing.getUserLockedFields() == 0 && hasDndAccess) {
+                boolean bypassDnd = channel.canBypassDnd();
+                existing.setBypassDnd(bypassDnd);
+
+                if (bypassDnd != mAreChannelsBypassingDnd) {
+                    updateChannelsBypassingDnd();
+                }
+            }
+
+            updateConfig();
+            return;
+        }
+        if (channel.getImportance() < IMPORTANCE_NONE
+                || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
+            throw new IllegalArgumentException("Invalid importance level");
+        }
+
+        // Reset fields that apps aren't allowed to set.
+        if (fromTargetApp && !hasDndAccess) {
+            channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+        }
+        if (fromTargetApp) {
+            channel.setLockscreenVisibility(r.visibility);
+        }
+        clearLockedFields(channel);
+        if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+            channel.setLockscreenVisibility(
+                    NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+        }
+        if (!r.showBadge) {
+            channel.setShowBadge(false);
+        }
+
+        r.channels.put(channel.getId(), channel);
+        if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+            updateChannelsBypassingDnd();
+        }
+        MetricsLogger.action(getChannelLog(channel, pkg).setType(
+                com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+    }
+
+    void clearLockedFields(NotificationChannel channel) {
+        channel.unlockFields(channel.getUserLockedFields());
+    }
+
+    @Override
+    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+            boolean fromUser) {
+        Preconditions.checkNotNull(updatedChannel);
+        Preconditions.checkNotNull(updatedChannel.getId());
+        PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+        if (r == null) {
+            throw new IllegalArgumentException("Invalid package");
+        }
+        NotificationChannel channel = r.channels.get(updatedChannel.getId());
+        if (channel == null || channel.isDeleted()) {
+            throw new IllegalArgumentException("Channel does not exist");
+        }
+        if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+            updatedChannel.setLockscreenVisibility(
+                    NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+        }
+        if (!fromUser) {
+            updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+        }
+        if (fromUser) {
+            updatedChannel.lockFields(channel.getUserLockedFields());
+            lockFieldsForUpdate(channel, updatedChannel);
+        }
+        r.channels.put(updatedChannel.getId(), updatedChannel);
+
+        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
+            // copy settings to app level so they are inherited by new channels
+            // when the app migrates
+            r.importance = updatedChannel.getImportance();
+            r.priority = updatedChannel.canBypassDnd()
+                    ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
+            r.visibility = updatedChannel.getLockscreenVisibility();
+            r.showBadge = updatedChannel.canShowBadge();
+        }
+
+        if (!channel.equals(updatedChannel)) {
+            // only log if there are real changes
+            MetricsLogger.action(getChannelLog(updatedChannel, pkg));
+        }
+
+        if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
+            updateChannelsBypassingDnd();
+        }
+        updateConfig();
+    }
+
+    @Override
+    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
+        PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+        if (r == null) {
+            return null;
+        }
+        if (channelId == null) {
+            channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+        }
+        final NotificationChannel nc = r.channels.get(channelId);
+        if (nc != null && (includeDeleted || !nc.isDeleted())) {
+            return nc;
+        }
+        return null;
+    }
+
+    @Override
+    public void deleteNotificationChannel(String pkg, int uid, String channelId) {
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return;
+        }
+        NotificationChannel channel = r.channels.get(channelId);
+        if (channel != null) {
+            channel.setDeleted(true);
+            LogMaker lm = getChannelLog(channel, pkg);
+            lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
+            MetricsLogger.action(lm);
+
+            if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
+                updateChannelsBypassingDnd();
+            }
+        }
+    }
+
+    @Override
+    @VisibleForTesting
+    public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(channelId);
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return;
+        }
+        r.channels.remove(channelId);
+    }
+
+    @Override
+    public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
+        Preconditions.checkNotNull(pkg);
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return;
+        }
+        int N = r.channels.size() - 1;
+        for (int i = N; i >= 0; i--) {
+            String key = r.channels.keyAt(i);
+            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+                r.channels.remove(key);
+            }
+        }
+    }
+
+    public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
+            int uid, String groupId, boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
+            return null;
+        }
+        NotificationChannelGroup group = r.groups.get(groupId).clone();
+        group.setChannels(new ArrayList<>());
+        int N = r.channels.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationChannel nc = r.channels.valueAt(i);
+            if (includeDeleted || !nc.isDeleted()) {
+                if (groupId.equals(nc.getGroup())) {
+                    group.addChannel(nc);
+                }
+            }
+        }
+        return group;
+    }
+
+    public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
+            int uid) {
+        Preconditions.checkNotNull(pkg);
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return null;
+        }
+        return r.groups.get(groupId);
+    }
+
+    @Override
+    public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+            int uid, boolean includeDeleted, boolean includeNonGrouped) {
+        Preconditions.checkNotNull(pkg);
+        Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return ParceledListSlice.emptyList();
+        }
+        NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+        int N = r.channels.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationChannel nc = r.channels.valueAt(i);
+            if (includeDeleted || !nc.isDeleted()) {
+                if (nc.getGroup() != null) {
+                    if (r.groups.get(nc.getGroup()) != null) {
+                        NotificationChannelGroup ncg = groups.get(nc.getGroup());
+                        if (ncg == null) {
+                            ncg = r.groups.get(nc.getGroup()).clone();
+                            ncg.setChannels(new ArrayList<>());
+                            groups.put(nc.getGroup(), ncg);
+
+                        }
+                        ncg.addChannel(nc);
+                    }
+                } else {
+                    nonGrouped.addChannel(nc);
+                }
+            }
+        }
+        if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
+            groups.put(null, nonGrouped);
+        }
+        return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+    }
+
+    public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
+            String groupId) {
+        List<NotificationChannel> deletedChannels = new ArrayList<>();
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null || TextUtils.isEmpty(groupId)) {
+            return deletedChannels;
+        }
+
+        r.groups.remove(groupId);
+
+        int N = r.channels.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationChannel nc = r.channels.valueAt(i);
+            if (groupId.equals(nc.getGroup())) {
+                nc.setDeleted(true);
+                deletedChannels.add(nc);
+            }
+        }
+        return deletedChannels;
+    }
+
+    @Override
+    public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+            int uid) {
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return new ArrayList<>();
+        }
+        return r.groups.values();
+    }
+
+    @Override
+    public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+            boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
+        List<NotificationChannel> channels = new ArrayList<>();
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return ParceledListSlice.emptyList();
+        }
+        int N = r.channels.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationChannel nc = r.channels.valueAt(i);
+            if (includeDeleted || !nc.isDeleted()) {
+                channels.add(nc);
+            }
+        }
+        return new ParceledListSlice<>(channels);
+    }
+
+    /**
+     * True for pre-O apps that only have the default channel, or pre O apps that have no
+     * channels yet. This method will create the default channel for pre-O apps that don't have it.
+     * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
+     * upgrades.
+     */
+    public boolean onlyHasDefaultChannel(String pkg, int uid) {
+        PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+        if (r.channels.size() == 1
+                && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            return true;
+        }
+        return false;
+    }
+
+    public int getDeletedChannelCount(String pkg, int uid) {
+        Preconditions.checkNotNull(pkg);
+        int deletedCount = 0;
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return deletedCount;
+        }
+        int N = r.channels.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationChannel nc = r.channels.valueAt(i);
+            if (nc.isDeleted()) {
+                deletedCount++;
+            }
+        }
+        return deletedCount;
+    }
+
+    public int getBlockedChannelCount(String pkg, int uid) {
+        Preconditions.checkNotNull(pkg);
+        int blockedCount = 0;
+        PackagePreferences r = getPackagePreferences(pkg, uid);
+        if (r == null) {
+            return blockedCount;
+        }
+        int N = r.channels.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationChannel nc = r.channels.valueAt(i);
+            if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
+                blockedCount++;
+            }
+        }
+        return blockedCount;
+    }
+
+    public int getBlockedAppCount(int userId) {
+        int count = 0;
+        synchronized (mPackagePreferencess) {
+            final int N = mPackagePreferencess.size();
+            for (int i = 0; i < N; i++) {
+                final PackagePreferences r = mPackagePreferencess.valueAt(i);
+                if (userId == UserHandle.getUserId(r.uid)
+                        && r.importance == IMPORTANCE_NONE) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
+    public void updateChannelsBypassingDnd() {
+        synchronized (mPackagePreferencess) {
+            final int numPackagePreferencess = mPackagePreferencess.size();
+            for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
+                    PackagePreferencesIndex++) {
+                final PackagePreferences r = mPackagePreferencess.valueAt(PackagePreferencesIndex);
+                final int numChannels = r.channels.size();
+
+                for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
+                    NotificationChannel channel = r.channels.valueAt(channelIndex);
+                    if (!channel.isDeleted() && channel.canBypassDnd()) {
+                        // If any channel bypasses DND, synchronize state and return early.
+                        if (!mAreChannelsBypassingDnd) {
+                            mAreChannelsBypassingDnd = true;
+                            updateZenPolicy(true);
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+
+        // If no channels bypass DND, update the zen policy once to disable DND bypass.
+        if (mAreChannelsBypassingDnd) {
+            mAreChannelsBypassingDnd = false;
+            updateZenPolicy(false);
+        }
+    }
+
+    public void updateZenPolicy(boolean areChannelsBypassingDnd) {
+        NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
+        mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
+                policy.priorityCategories, policy.priorityCallSenders,
+                policy.priorityMessageSenders, policy.suppressedVisualEffects,
+                (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
+                        : 0)));
+    }
+
+    public boolean areChannelsBypassingDnd() {
+        return mAreChannelsBypassingDnd;
+    }
+
+    /**
+     * Sets importance.
+     */
+    @Override
+    public void setImportance(String pkgName, int uid, int importance) {
+        getOrCreatePackagePreferences(pkgName, uid).importance = importance;
+        updateConfig();
+    }
+
+    public void setEnabled(String packageName, int uid, boolean enabled) {
+        boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
+        if (wasEnabled == enabled) {
+            return;
+        }
+        setImportance(packageName, uid,
+                enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
+    }
+
+    /**
+     * Sets whether any notifications from the app, represented by the given {@code pkgName} and
+     * {@code uid}, have their importance locked by the user. Locked notifications don't get
+     * considered for sentiment adjustments (and thus never show a blocking helper).
+     */
+    public void setAppImportanceLocked(String packageName, int uid) {
+        PackagePreferences PackagePreferences = getOrCreatePackagePreferences(packageName, uid);
+        if ((PackagePreferences.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
+            return;
+        }
+
+        PackagePreferences.lockedAppFields =
+                PackagePreferences.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
+        updateConfig();
+    }
+
+    @VisibleForTesting
+    void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
+        if (original.canBypassDnd() != update.canBypassDnd()) {
+            update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        }
+        if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
+            update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+        }
+        if (original.getImportance() != update.getImportance()) {
+            update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+        }
+        if (original.shouldShowLights() != update.shouldShowLights()
+                || original.getLightColor() != update.getLightColor()) {
+            update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
+        }
+        if (!Objects.equals(original.getSound(), update.getSound())) {
+            update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+        }
+        if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
+                || original.shouldVibrate() != update.shouldVibrate()) {
+            update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
+        }
+        if (original.canShowBadge() != update.canShowBadge()) {
+            update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix,
+            @NonNull NotificationManagerService.DumpFilter filter) {
+        pw.print(prefix);
+        pw.println("per-package config:");
+
+        pw.println("PackagePreferencess:");
+        synchronized (mPackagePreferencess) {
+            dumpPackagePreferencess(pw, prefix, filter, mPackagePreferencess);
+        }
+        pw.println("Restored without uid:");
+        dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
+    }
+
+    public void dump(ProtoOutputStream proto,
+            @NonNull NotificationManagerService.DumpFilter filter) {
+        synchronized (mPackagePreferencess) {
+            dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
+                    mPackagePreferencess);
+        }
+        dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+                mRestoredWithoutUids);
+    }
+
+    private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
+            @NonNull NotificationManagerService.DumpFilter filter,
+            ArrayMap<String, PackagePreferences> PackagePreferencess) {
+        final int N = PackagePreferencess.size();
+        for (int i = 0; i < N; i++) {
+            final PackagePreferences r = PackagePreferencess.valueAt(i);
+            if (filter.matches(r.pkg)) {
+                pw.print(prefix);
+                pw.print("  AppSettings: ");
+                pw.print(r.pkg);
+                pw.print(" (");
+                pw.print(r.uid == PackagePreferences.UNKNOWN_UID ? "UNKNOWN_UID"
+                        : Integer.toString(r.uid));
+                pw.print(')');
+                if (r.importance != DEFAULT_IMPORTANCE) {
+                    pw.print(" importance=");
+                    pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
+                }
+                if (r.priority != DEFAULT_PRIORITY) {
+                    pw.print(" priority=");
+                    pw.print(Notification.priorityToString(r.priority));
+                }
+                if (r.visibility != DEFAULT_VISIBILITY) {
+                    pw.print(" visibility=");
+                    pw.print(Notification.visibilityToString(r.visibility));
+                }
+                pw.print(" showBadge=");
+                pw.print(Boolean.toString(r.showBadge));
+                pw.println();
+                for (NotificationChannel channel : r.channels.values()) {
+                    pw.print(prefix);
+                    channel.dump(pw, "    ", filter.redact);
+                }
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    pw.print(prefix);
+                    pw.print("  ");
+                    pw.print("  ");
+                    pw.println(group);
+                }
+            }
+        }
+    }
+
+    private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
+            @NonNull NotificationManagerService.DumpFilter filter,
+            ArrayMap<String, PackagePreferences> PackagePreferencess) {
+        final int N = PackagePreferencess.size();
+        long fToken;
+        for (int i = 0; i < N; i++) {
+            final PackagePreferences r = PackagePreferencess.valueAt(i);
+            if (filter.matches(r.pkg)) {
+                fToken = proto.start(fieldId);
+
+                proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
+                proto.write(RankingHelperProto.RecordProto.UID, r.uid);
+                proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
+                proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
+                proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
+                proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
+
+                for (NotificationChannel channel : r.channels.values()) {
+                    channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
+                }
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
+                }
+
+                proto.end(fToken);
+            }
+        }
+    }
+
+    public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
+        JSONObject ranking = new JSONObject();
+        JSONArray PackagePreferencess = new JSONArray();
+        try {
+            ranking.put("noUid", mRestoredWithoutUids.size());
+        } catch (JSONException e) {
+            // pass
+        }
+        synchronized (mPackagePreferencess) {
+            final int N = mPackagePreferencess.size();
+            for (int i = 0; i < N; i++) {
+                final PackagePreferences r = mPackagePreferencess.valueAt(i);
+                if (filter == null || filter.matches(r.pkg)) {
+                    JSONObject PackagePreferences = new JSONObject();
+                    try {
+                        PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
+                        PackagePreferences.put("packageName", r.pkg);
+                        if (r.importance != DEFAULT_IMPORTANCE) {
+                            PackagePreferences.put("importance",
+                                    NotificationListenerService.Ranking.importanceToString(
+                                            r.importance));
+                        }
+                        if (r.priority != DEFAULT_PRIORITY) {
+                            PackagePreferences.put("priority",
+                                    Notification.priorityToString(r.priority));
+                        }
+                        if (r.visibility != DEFAULT_VISIBILITY) {
+                            PackagePreferences.put("visibility",
+                                    Notification.visibilityToString(r.visibility));
+                        }
+                        if (r.showBadge != DEFAULT_SHOW_BADGE) {
+                            PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
+                        }
+                        JSONArray channels = new JSONArray();
+                        for (NotificationChannel channel : r.channels.values()) {
+                            channels.put(channel.toJson());
+                        }
+                        PackagePreferences.put("channels", channels);
+                        JSONArray groups = new JSONArray();
+                        for (NotificationChannelGroup group : r.groups.values()) {
+                            groups.put(group.toJson());
+                        }
+                        PackagePreferences.put("groups", groups);
+                    } catch (JSONException e) {
+                        // pass
+                    }
+                    PackagePreferencess.put(PackagePreferences);
+                }
+            }
+        }
+        try {
+            ranking.put("PackagePreferencess", PackagePreferencess);
+        } catch (JSONException e) {
+            // pass
+        }
+        return ranking;
+    }
+
+    /**
+     * Dump only the ban information as structured JSON for the stats collector.
+     *
+     * This is intentionally redundant with {#link dumpJson} because the old
+     * scraper will expect this format.
+     *
+     * @param filter
+     * @return
+     */
+    public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
+        JSONArray bans = new JSONArray();
+        Map<Integer, String> packageBans = getPackageBans();
+        for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
+            final int userId = UserHandle.getUserId(ban.getKey());
+            final String packageName = ban.getValue();
+            if (filter == null || filter.matches(packageName)) {
+                JSONObject banJson = new JSONObject();
+                try {
+                    banJson.put("userId", userId);
+                    banJson.put("packageName", packageName);
+                } catch (JSONException e) {
+                    e.printStackTrace();
+                }
+                bans.put(banJson);
+            }
+        }
+        return bans;
+    }
+
+    public Map<Integer, String> getPackageBans() {
+        synchronized (mPackagePreferencess) {
+            final int N = mPackagePreferencess.size();
+            ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
+            for (int i = 0; i < N; i++) {
+                final PackagePreferences r = mPackagePreferencess.valueAt(i);
+                if (r.importance == IMPORTANCE_NONE) {
+                    packageBans.put(r.uid, r.pkg);
+                }
+            }
+
+            return packageBans;
+        }
+    }
+
+    /**
+     * Dump only the channel information as structured JSON for the stats collector.
+     *
+     * This is intentionally redundant with {#link dumpJson} because the old
+     * scraper will expect this format.
+     *
+     * @param filter
+     * @return
+     */
+    public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
+        JSONArray channels = new JSONArray();
+        Map<String, Integer> packageChannels = getPackageChannels();
+        for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
+            final String packageName = channelCount.getKey();
+            if (filter == null || filter.matches(packageName)) {
+                JSONObject channelCountJson = new JSONObject();
+                try {
+                    channelCountJson.put("packageName", packageName);
+                    channelCountJson.put("channelCount", channelCount.getValue());
+                } catch (JSONException e) {
+                    e.printStackTrace();
+                }
+                channels.put(channelCountJson);
+            }
+        }
+        return channels;
+    }
+
+    private Map<String, Integer> getPackageChannels() {
+        ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
+        synchronized (mPackagePreferencess) {
+            for (int i = 0; i < mPackagePreferencess.size(); i++) {
+                final PackagePreferences r = mPackagePreferencess.valueAt(i);
+                int channelCount = 0;
+                for (int j = 0; j < r.channels.size(); j++) {
+                    if (!r.channels.valueAt(j).isDeleted()) {
+                        channelCount++;
+                    }
+                }
+                packageChannels.put(r.pkg, channelCount);
+            }
+        }
+        return packageChannels;
+    }
+
+    public void onUserRemoved(int userId) {
+        synchronized (mPackagePreferencess) {
+            int N = mPackagePreferencess.size();
+            for (int i = N - 1; i >= 0; i--) {
+                PackagePreferences PackagePreferences = mPackagePreferencess.valueAt(i);
+                if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
+                    mPackagePreferencess.removeAt(i);
+                }
+            }
+        }
+    }
+
+    protected void onLocaleChanged(Context context, int userId) {
+        synchronized (mPackagePreferencess) {
+            int N = mPackagePreferencess.size();
+            for (int i = 0; i < N; i++) {
+                PackagePreferences PackagePreferences = mPackagePreferencess.valueAt(i);
+                if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
+                    if (PackagePreferences.channels.containsKey(
+                            NotificationChannel.DEFAULT_CHANNEL_ID)) {
+                        PackagePreferences.channels.get(
+                                NotificationChannel.DEFAULT_CHANNEL_ID).setName(
+                                context.getResources().getString(
+                                        R.string.default_notification_channel_label));
+                    }
+                }
+            }
+        }
+    }
+
+    public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
+            int[] uidList) {
+        if (pkgList == null || pkgList.length == 0) {
+            return; // nothing to do
+        }
+        boolean updated = false;
+        if (removingPackage) {
+            // Remove notification settings for uninstalled package
+            int size = Math.min(pkgList.length, uidList.length);
+            for (int i = 0; i < size; i++) {
+                final String pkg = pkgList[i];
+                final int uid = uidList[i];
+                synchronized (mPackagePreferencess) {
+                    mPackagePreferencess.remove(packagePreferencesKey(pkg, uid));
+                }
+                mRestoredWithoutUids.remove(pkg);
+                updated = true;
+            }
+        } else {
+            for (String pkg : pkgList) {
+                // Package install
+                final PackagePreferences r = mRestoredWithoutUids.get(pkg);
+                if (r != null) {
+                    try {
+                        r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
+                        mRestoredWithoutUids.remove(pkg);
+                        synchronized (mPackagePreferencess) {
+                            mPackagePreferencess.put(packagePreferencesKey(r.pkg, r.uid), r);
+                        }
+                        updated = true;
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // noop
+                    }
+                }
+                // Package upgrade
+                try {
+                    PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
+                            mPm.getPackageUidAsUser(pkg, changeUserId));
+                    if (fullPackagePreferences != null) {
+                        createDefaultChannelIfNeeded(fullPackagePreferences);
+                        deleteDefaultChannelIfNeeded(fullPackagePreferences);
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                }
+            }
+        }
+
+        if (updated) {
+            updateConfig();
+        }
+    }
+
+    private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
+        return new LogMaker(
+                com.android.internal.logging.nano.MetricsProto.MetricsEvent
+                        .ACTION_NOTIFICATION_CHANNEL)
+                .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
+                .setPackageName(pkg)
+                .addTaggedData(
+                        com.android.internal.logging.nano.MetricsProto.MetricsEvent
+                                .FIELD_NOTIFICATION_CHANNEL_ID,
+                        channel.getId())
+                .addTaggedData(
+                        com.android.internal.logging.nano.MetricsProto.MetricsEvent
+                                .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
+                        channel.getImportance());
+    }
+
+    private LogMaker getChannelGroupLog(String groupId, String pkg) {
+        return new LogMaker(
+                com.android.internal.logging.nano.MetricsProto.MetricsEvent
+                        .ACTION_NOTIFICATION_CHANNEL_GROUP)
+                .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
+                .addTaggedData(
+                        com.android.internal.logging.nano.MetricsProto.MetricsEvent
+                                .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
+                        groupId)
+                .setPackageName(pkg);
+    }
+
+
+    public void updateBadgingEnabled() {
+        if (mBadgingEnabled == null) {
+            mBadgingEnabled = new SparseBooleanArray();
+        }
+        boolean changed = false;
+        // update the cached values
+        for (int index = 0; index < mBadgingEnabled.size(); index++) {
+            int userId = mBadgingEnabled.keyAt(index);
+            final boolean oldValue = mBadgingEnabled.get(userId);
+            final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.NOTIFICATION_BADGING,
+                    DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
+            mBadgingEnabled.put(userId, newValue);
+            changed |= oldValue != newValue;
+        }
+        if (changed) {
+            updateConfig();
+        }
+    }
+
+    public boolean badgingEnabled(UserHandle userHandle) {
+        int userId = userHandle.getIdentifier();
+        if (userId == UserHandle.USER_ALL) {
+            return false;
+        }
+        if (mBadgingEnabled.indexOfKey(userId) < 0) {
+            mBadgingEnabled.put(userId,
+                    Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                            Settings.Secure.NOTIFICATION_BADGING,
+                            DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
+        }
+        return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
+    }
+
+    private void updateConfig() {
+        mRankingHandler.requestSort();
+    }
+
+    private static String packagePreferencesKey(String pkg, int uid) {
+        return pkg + "|" + uid;
+    }
+
+    private static class PackagePreferences {
+        static int UNKNOWN_UID = UserHandle.USER_NULL;
+
+        String pkg;
+        int uid = UNKNOWN_UID;
+        int importance = DEFAULT_IMPORTANCE;
+        int priority = DEFAULT_PRIORITY;
+        int visibility = DEFAULT_VISIBILITY;
+        boolean showBadge = DEFAULT_SHOW_BADGE;
+        int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+
+        ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
+        Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
+    }
+}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 61b5415..f5e58ea 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -15,123 +15,37 @@
  */
 package com.android.server.notification;
 
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ParceledListSlice;
-import android.metrics.LogMaker;
-import android.os.Build;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.RankingHelperProto;
-import android.service.notification.RankingHelperProto.RecordProto;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
-import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
 
-public class RankingHelper implements RankingConfig {
+public class RankingHelper {
     private static final String TAG = "RankingHelper";
 
-    private static final int XML_VERSION = 1;
-
-    static final String TAG_RANKING = "ranking";
-    private static final String TAG_PACKAGE = "package";
-    private static final String TAG_CHANNEL = "channel";
-    private static final String TAG_GROUP = "channelGroup";
-
-    private static final String ATT_VERSION = "version";
-    private static final String ATT_NAME = "name";
-    private static final String ATT_UID = "uid";
-    private static final String ATT_ID = "id";
-    private static final String ATT_PRIORITY = "priority";
-    private static final String ATT_VISIBILITY = "visibility";
-    private static final String ATT_IMPORTANCE = "importance";
-    private static final String ATT_SHOW_BADGE = "show_badge";
-    private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
-
-    private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
-    private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
-    private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
-    private static final boolean DEFAULT_SHOW_BADGE = true;
-    /**
-     * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
-     * fields.
-     */
-    private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
-
-    /**
-     * All user-lockable fields for a given application.
-     */
-    @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
-    public @interface LockableAppFields {
-        int USER_LOCKED_IMPORTANCE = 0x00000001;
-    }
-
     private final NotificationSignalExtractor[] mSignalExtractors;
     private final NotificationComparator mPreliminaryComparator;
     private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
 
-    private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
     private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
-    private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
 
     private final Context mContext;
     private final RankingHandler mRankingHandler;
-    private final PackageManager mPm;
-    private SparseBooleanArray mBadgingEnabled;
 
-    private boolean mAreChannelsBypassingDnd;
-    private ZenModeHelper mZenModeHelper;
 
-    public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+    public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
             ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
         mContext = context;
         mRankingHandler = rankingHandler;
-        mPm = pm;
-        mZenModeHelper= zenHelper;
-
         mPreliminaryComparator = new NotificationComparator(mContext);
 
-        updateBadgingEnabled();
-
         final int N = extractorNames.length;
         mSignalExtractors = new NotificationSignalExtractor[N];
         for (int i = 0; i < N; i++) {
@@ -140,7 +54,7 @@
                 NotificationSignalExtractor extractor =
                         (NotificationSignalExtractor) extractorClass.newInstance();
                 extractor.initialize(mContext, usageStats);
-                extractor.setConfig(this);
+                extractor.setConfig(config);
                 extractor.setZenHelper(zenHelper);
                 mSignalExtractors[i] = extractor;
             } catch (ClassNotFoundException e) {
@@ -151,10 +65,6 @@
                 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
             }
         }
-
-        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
-        updateChannelsBypassingDnd();
     }
 
     @SuppressWarnings("unchecked")
@@ -184,279 +94,6 @@
         }
     }
 
-    public void readXml(XmlPullParser parser, boolean forRestore)
-            throws XmlPullParserException, IOException {
-        int type = parser.getEventType();
-        if (type != XmlPullParser.START_TAG) return;
-        String tag = parser.getName();
-        if (!TAG_RANKING.equals(tag)) return;
-        // Clobber groups and channels with the xml, but don't delete other data that wasn't present
-        // at the time of serialization.
-        mRestoredWithoutUids.clear();
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            tag = parser.getName();
-            if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
-                return;
-            }
-            if (type == XmlPullParser.START_TAG) {
-                if (TAG_PACKAGE.equals(tag)) {
-                    int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
-                    String name = parser.getAttributeValue(null, ATT_NAME);
-                    if (!TextUtils.isEmpty(name)) {
-                        if (forRestore) {
-                            try {
-                                //TODO: http://b/22388012
-                                uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
-                            } catch (NameNotFoundException e) {
-                                // noop
-                            }
-                        }
-
-                        Record r = getOrCreateRecord(name, uid,
-                                XmlUtils.readIntAttribute(
-                                        parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
-                                XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
-                                XmlUtils.readIntAttribute(
-                                        parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
-                                XmlUtils.readBooleanAttribute(
-                                        parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
-                        r.importance = XmlUtils.readIntAttribute(
-                                parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
-                        r.priority = XmlUtils.readIntAttribute(
-                                parser, ATT_PRIORITY, DEFAULT_PRIORITY);
-                        r.visibility = XmlUtils.readIntAttribute(
-                                parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
-                        r.showBadge = XmlUtils.readBooleanAttribute(
-                                parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
-                        r.lockedAppFields = XmlUtils.readIntAttribute(parser,
-                                ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
-
-                        final int innerDepth = parser.getDepth();
-                        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                                && (type != XmlPullParser.END_TAG
-                                || parser.getDepth() > innerDepth)) {
-                            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                                continue;
-                            }
-
-                            String tagName = parser.getName();
-                            // Channel groups
-                            if (TAG_GROUP.equals(tagName)) {
-                                String id = parser.getAttributeValue(null, ATT_ID);
-                                CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
-                                if (!TextUtils.isEmpty(id)) {
-                                    NotificationChannelGroup group
-                                            = new NotificationChannelGroup(id, groupName);
-                                    group.populateFromXml(parser);
-                                    r.groups.put(id, group);
-                                }
-                            }
-                            // Channels
-                            if (TAG_CHANNEL.equals(tagName)) {
-                                String id = parser.getAttributeValue(null, ATT_ID);
-                                String channelName = parser.getAttributeValue(null, ATT_NAME);
-                                int channelImportance = XmlUtils.readIntAttribute(
-                                        parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
-                                if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
-                                    NotificationChannel channel = new NotificationChannel(id,
-                                            channelName, channelImportance);
-                                    if (forRestore) {
-                                        channel.populateFromXmlForRestore(parser, mContext);
-                                    } else {
-                                        channel.populateFromXml(parser);
-                                    }
-                                    r.channels.put(id, channel);
-                                }
-                            }
-                        }
-
-                        try {
-                            deleteDefaultChannelIfNeeded(r);
-                        } catch (NameNotFoundException e) {
-                            Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
-                        }
-                    }
-                }
-            }
-        }
-        throw new IllegalStateException("Failed to reach END_DOCUMENT");
-    }
-
-    private static String recordKey(String pkg, int uid) {
-        return pkg + "|" + uid;
-    }
-
-    private Record getRecord(String pkg, int uid) {
-        final String key = recordKey(pkg, uid);
-        synchronized (mRecords) {
-            return mRecords.get(key);
-        }
-    }
-
-    private Record getOrCreateRecord(String pkg, int uid) {
-        return getOrCreateRecord(pkg, uid,
-                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
-    }
-
-    private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
-            int visibility, boolean showBadge) {
-        final String key = recordKey(pkg, uid);
-        synchronized (mRecords) {
-            Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
-                    key);
-            if (r == null) {
-                r = new Record();
-                r.pkg = pkg;
-                r.uid = uid;
-                r.importance = importance;
-                r.priority = priority;
-                r.visibility = visibility;
-                r.showBadge = showBadge;
-
-                try {
-                    createDefaultChannelIfNeeded(r);
-                } catch (NameNotFoundException e) {
-                    Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
-                }
-
-                if (r.uid == Record.UNKNOWN_UID) {
-                    mRestoredWithoutUids.put(pkg, r);
-                } else {
-                    mRecords.put(key, r);
-                }
-            }
-            return r;
-        }
-    }
-
-    private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
-        final int userId = UserHandle.getUserId(r.uid);
-        final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
-        if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
-            // O apps should not have the default channel.
-            return false;
-        }
-
-        // Otherwise, this app should have the default channel.
-        return true;
-    }
-
-    private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
-        if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            // Not present
-            return;
-        }
-
-        if (shouldHaveDefaultChannel(r)) {
-            // Keep the default channel until upgraded.
-            return;
-        }
-
-        // Remove Default Channel.
-        r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
-    }
-
-    private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
-        if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
-                    mContext.getString(R.string.default_notification_channel_label));
-            return;
-        }
-
-        if (!shouldHaveDefaultChannel(r)) {
-            // Keep the default channel until upgraded.
-            return;
-        }
-
-        // Create Default Channel
-        NotificationChannel channel;
-        channel = new NotificationChannel(
-                NotificationChannel.DEFAULT_CHANNEL_ID,
-                mContext.getString(R.string.default_notification_channel_label),
-                r.importance);
-        channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
-        channel.setLockscreenVisibility(r.visibility);
-        if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
-            channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-        }
-        if (r.priority != DEFAULT_PRIORITY) {
-            channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
-        }
-        if (r.visibility != DEFAULT_VISIBILITY) {
-            channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
-        }
-        r.channels.put(channel.getId(), channel);
-    }
-
-    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
-        out.startTag(null, TAG_RANKING);
-        out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
-
-        synchronized (mRecords) {
-            final int N = mRecords.size();
-            for (int i = 0; i < N; i++) {
-                final Record r = mRecords.valueAt(i);
-                //TODO: http://b/22388012
-                if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
-                    continue;
-                }
-                final boolean hasNonDefaultSettings =
-                        r.importance != DEFAULT_IMPORTANCE
-                            || r.priority != DEFAULT_PRIORITY
-                            || r.visibility != DEFAULT_VISIBILITY
-                            || r.showBadge != DEFAULT_SHOW_BADGE
-                            || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
-                            || r.channels.size() > 0
-                            || r.groups.size() > 0;
-                if (hasNonDefaultSettings) {
-                    out.startTag(null, TAG_PACKAGE);
-                    out.attribute(null, ATT_NAME, r.pkg);
-                    if (r.importance != DEFAULT_IMPORTANCE) {
-                        out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
-                    }
-                    if (r.priority != DEFAULT_PRIORITY) {
-                        out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
-                    }
-                    if (r.visibility != DEFAULT_VISIBILITY) {
-                        out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
-                    }
-                    out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
-                    out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
-                            Integer.toString(r.lockedAppFields));
-
-                    if (!forBackup) {
-                        out.attribute(null, ATT_UID, Integer.toString(r.uid));
-                    }
-
-                    for (NotificationChannelGroup group : r.groups.values()) {
-                        group.writeXml(out);
-                    }
-
-                    for (NotificationChannel channel : r.channels.values()) {
-                        if (forBackup) {
-                            if (!channel.isDeleted()) {
-                                channel.writeXmlForBackup(out, mContext);
-                            }
-                        } else {
-                            channel.writeXml(out);
-                        }
-                    }
-
-                    out.endTag(null, TAG_PACKAGE);
-                }
-            }
-        }
-        out.endTag(null, TAG_RANKING);
-    }
-
-    private void updateConfig() {
-        final int N = mSignalExtractors.length;
-        for (int i = 0; i < N; i++) {
-            mSignalExtractors[i].setConfig(this);
-        }
-        mRankingHandler.requestSort();
-    }
-
     public void sort(ArrayList<NotificationRecord> notificationList) {
         final int N = notificationList.size();
         // clear global sort keys
@@ -521,562 +158,6 @@
         return Collections.binarySearch(notificationList, target, mFinalComparator);
     }
 
-    /**
-     * Gets importance.
-     */
-    @Override
-    public int getImportance(String packageName, int uid) {
-        return getOrCreateRecord(packageName, uid).importance;
-    }
-
-
-    /**
-     * Returns whether the importance of the corresponding notification is user-locked and shouldn't
-     * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
-     * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
-     */
-    public boolean getIsAppImportanceLocked(String packageName, int uid) {
-        int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
-        return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
-    }
-
-    @Override
-    public boolean canShowBadge(String packageName, int uid) {
-        return getOrCreateRecord(packageName, uid).showBadge;
-    }
-
-    @Override
-    public void setShowBadge(String packageName, int uid, boolean showBadge) {
-        getOrCreateRecord(packageName, uid).showBadge = showBadge;
-        updateConfig();
-    }
-
-    @Override
-    public boolean isGroupBlocked(String packageName, int uid, String groupId) {
-        if (groupId == null) {
-            return false;
-        }
-        Record r = getOrCreateRecord(packageName, uid);
-        NotificationChannelGroup group = r.groups.get(groupId);
-        if (group == null) {
-            return false;
-        }
-        return group.isBlocked();
-    }
-
-    int getPackagePriority(String pkg, int uid) {
-        return getOrCreateRecord(pkg, uid).priority;
-    }
-
-    int getPackageVisibility(String pkg, int uid) {
-        return getOrCreateRecord(pkg, uid).visibility;
-    }
-
-    @Override
-    public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
-            boolean fromTargetApp) {
-        Preconditions.checkNotNull(pkg);
-        Preconditions.checkNotNull(group);
-        Preconditions.checkNotNull(group.getId());
-        Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
-        Record r = getOrCreateRecord(pkg, uid);
-        if (r == null) {
-            throw new IllegalArgumentException("Invalid package");
-        }
-        final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
-        if (!group.equals(oldGroup)) {
-            // will log for new entries as well as name/description changes
-            MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
-        }
-        if (oldGroup != null) {
-            group.setChannels(oldGroup.getChannels());
-
-            if (fromTargetApp) {
-                group.setBlocked(oldGroup.isBlocked());
-            }
-        }
-        r.groups.put(group.getId(), group);
-    }
-
-    @Override
-    public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
-            boolean fromTargetApp, boolean hasDndAccess) {
-        Preconditions.checkNotNull(pkg);
-        Preconditions.checkNotNull(channel);
-        Preconditions.checkNotNull(channel.getId());
-        Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
-        Record r = getOrCreateRecord(pkg, uid);
-        if (r == null) {
-            throw new IllegalArgumentException("Invalid package");
-        }
-        if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
-            throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
-        }
-        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
-            throw new IllegalArgumentException("Reserved id");
-        }
-        NotificationChannel existing = r.channels.get(channel.getId());
-        // Keep most of the existing settings
-        if (existing != null && fromTargetApp) {
-            if (existing.isDeleted()) {
-                existing.setDeleted(false);
-
-                // log a resurrected channel as if it's new again
-                MetricsLogger.action(getChannelLog(channel, pkg).setType(
-                        MetricsProto.MetricsEvent.TYPE_OPEN));
-            }
-
-            existing.setName(channel.getName().toString());
-            existing.setDescription(channel.getDescription());
-            existing.setBlockableSystem(channel.isBlockableSystem());
-            if (existing.getGroup() == null) {
-                existing.setGroup(channel.getGroup());
-            }
-
-            // Apps are allowed to downgrade channel importance if the user has not changed any
-            // fields on this channel yet.
-            if (existing.getUserLockedFields() == 0 &&
-                    channel.getImportance() < existing.getImportance()) {
-                existing.setImportance(channel.getImportance());
-            }
-
-            // system apps and dnd access apps can bypass dnd if the user hasn't changed any
-            // fields on the channel yet
-            if (existing.getUserLockedFields() == 0 && hasDndAccess) {
-                boolean bypassDnd = channel.canBypassDnd();
-                existing.setBypassDnd(bypassDnd);
-
-                if (bypassDnd != mAreChannelsBypassingDnd) {
-                    updateChannelsBypassingDnd();
-                }
-            }
-
-            updateConfig();
-            return;
-        }
-        if (channel.getImportance() < IMPORTANCE_NONE
-                || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
-            throw new IllegalArgumentException("Invalid importance level");
-        }
-
-        // Reset fields that apps aren't allowed to set.
-        if (fromTargetApp && !hasDndAccess) {
-            channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
-        }
-        if (fromTargetApp) {
-            channel.setLockscreenVisibility(r.visibility);
-        }
-        clearLockedFields(channel);
-        if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
-            channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
-        }
-        if (!r.showBadge) {
-            channel.setShowBadge(false);
-        }
-
-        r.channels.put(channel.getId(), channel);
-        if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd();
-        }
-        MetricsLogger.action(getChannelLog(channel, pkg).setType(
-                MetricsProto.MetricsEvent.TYPE_OPEN));
-    }
-
-    void clearLockedFields(NotificationChannel channel) {
-        channel.unlockFields(channel.getUserLockedFields());
-    }
-
-    @Override
-    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
-            boolean fromUser) {
-        Preconditions.checkNotNull(updatedChannel);
-        Preconditions.checkNotNull(updatedChannel.getId());
-        Record r = getOrCreateRecord(pkg, uid);
-        if (r == null) {
-            throw new IllegalArgumentException("Invalid package");
-        }
-        NotificationChannel channel = r.channels.get(updatedChannel.getId());
-        if (channel == null || channel.isDeleted()) {
-            throw new IllegalArgumentException("Channel does not exist");
-        }
-        if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
-            updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
-        }
-        if (!fromUser) {
-            updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
-        }
-        if (fromUser) {
-            updatedChannel.lockFields(channel.getUserLockedFields());
-            lockFieldsForUpdate(channel, updatedChannel);
-        }
-        r.channels.put(updatedChannel.getId(), updatedChannel);
-
-        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
-            // copy settings to app level so they are inherited by new channels
-            // when the app migrates
-            r.importance = updatedChannel.getImportance();
-            r.priority = updatedChannel.canBypassDnd()
-                    ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
-            r.visibility = updatedChannel.getLockscreenVisibility();
-            r.showBadge = updatedChannel.canShowBadge();
-        }
-
-        if (!channel.equals(updatedChannel)) {
-            // only log if there are real changes
-            MetricsLogger.action(getChannelLog(updatedChannel, pkg));
-        }
-
-        if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd();
-        }
-        updateConfig();
-    }
-
-    @Override
-    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
-            boolean includeDeleted) {
-        Preconditions.checkNotNull(pkg);
-        Record r = getOrCreateRecord(pkg, uid);
-        if (r == null) {
-            return null;
-        }
-        if (channelId == null) {
-            channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
-        }
-        final NotificationChannel nc = r.channels.get(channelId);
-        if (nc != null && (includeDeleted || !nc.isDeleted())) {
-            return nc;
-        }
-        return null;
-    }
-
-    @Override
-    public void deleteNotificationChannel(String pkg, int uid, String channelId) {
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return;
-        }
-        NotificationChannel channel = r.channels.get(channelId);
-        if (channel != null) {
-            channel.setDeleted(true);
-            LogMaker lm = getChannelLog(channel, pkg);
-            lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
-            MetricsLogger.action(lm);
-
-            if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
-                updateChannelsBypassingDnd();
-            }
-        }
-    }
-
-    @Override
-    @VisibleForTesting
-    public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
-        Preconditions.checkNotNull(pkg);
-        Preconditions.checkNotNull(channelId);
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return;
-        }
-        r.channels.remove(channelId);
-    }
-
-    @Override
-    public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
-        Preconditions.checkNotNull(pkg);
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return;
-        }
-        int N = r.channels.size() - 1;
-        for (int i = N; i >= 0; i--) {
-            String key = r.channels.keyAt(i);
-            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
-                r.channels.remove(key);
-            }
-        }
-    }
-
-    public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
-            int uid, String groupId, boolean includeDeleted) {
-        Preconditions.checkNotNull(pkg);
-        Record r = getRecord(pkg, uid);
-        if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
-            return null;
-        }
-        NotificationChannelGroup group = r.groups.get(groupId).clone();
-        group.setChannels(new ArrayList<>());
-        int N = r.channels.size();
-        for (int i = 0; i < N; i++) {
-            final NotificationChannel nc = r.channels.valueAt(i);
-            if (includeDeleted || !nc.isDeleted()) {
-                if (groupId.equals(nc.getGroup())) {
-                    group.addChannel(nc);
-                }
-            }
-        }
-        return group;
-    }
-
-    public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
-            int uid) {
-        Preconditions.checkNotNull(pkg);
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return null;
-        }
-        return r.groups.get(groupId);
-    }
-
-    @Override
-    public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
-            int uid, boolean includeDeleted, boolean includeNonGrouped) {
-        Preconditions.checkNotNull(pkg);
-        Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return ParceledListSlice.emptyList();
-        }
-        NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
-        int N = r.channels.size();
-        for (int i = 0; i < N; i++) {
-            final NotificationChannel nc = r.channels.valueAt(i);
-            if (includeDeleted || !nc.isDeleted()) {
-                if (nc.getGroup() != null) {
-                    if (r.groups.get(nc.getGroup()) != null) {
-                        NotificationChannelGroup ncg = groups.get(nc.getGroup());
-                        if (ncg == null) {
-                            ncg = r.groups.get(nc.getGroup()).clone();
-                            ncg.setChannels(new ArrayList<>());
-                            groups.put(nc.getGroup(), ncg);
-
-                        }
-                        ncg.addChannel(nc);
-                    }
-                } else {
-                    nonGrouped.addChannel(nc);
-                }
-            }
-        }
-        if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
-            groups.put(null, nonGrouped);
-        }
-        return new ParceledListSlice<>(new ArrayList<>(groups.values()));
-    }
-
-    public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
-            String groupId) {
-        List<NotificationChannel> deletedChannels = new ArrayList<>();
-        Record r = getRecord(pkg, uid);
-        if (r == null || TextUtils.isEmpty(groupId)) {
-            return deletedChannels;
-        }
-
-        r.groups.remove(groupId);
-
-        int N = r.channels.size();
-        for (int i = 0; i < N; i++) {
-            final NotificationChannel nc = r.channels.valueAt(i);
-            if (groupId.equals(nc.getGroup())) {
-                nc.setDeleted(true);
-                deletedChannels.add(nc);
-            }
-        }
-        return deletedChannels;
-    }
-
-    @Override
-    public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
-            int uid) {
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return new ArrayList<>();
-        }
-        return r.groups.values();
-    }
-
-    @Override
-    public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
-            boolean includeDeleted) {
-        Preconditions.checkNotNull(pkg);
-        List<NotificationChannel> channels = new ArrayList<>();
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return ParceledListSlice.emptyList();
-        }
-        int N = r.channels.size();
-        for (int i = 0; i < N; i++) {
-            final NotificationChannel nc = r.channels.valueAt(i);
-            if (includeDeleted || !nc.isDeleted()) {
-                channels.add(nc);
-            }
-        }
-        return new ParceledListSlice<>(channels);
-    }
-
-    /**
-     * True for pre-O apps that only have the default channel, or pre O apps that have no
-     * channels yet. This method will create the default channel for pre-O apps that don't have it.
-     * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
-     * upgrades.
-     */
-    public boolean onlyHasDefaultChannel(String pkg, int uid) {
-        Record r = getOrCreateRecord(pkg, uid);
-        if (r.channels.size() == 1
-                && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            return true;
-        }
-        return false;
-    }
-
-    public int getDeletedChannelCount(String pkg, int uid) {
-        Preconditions.checkNotNull(pkg);
-        int deletedCount = 0;
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return deletedCount;
-        }
-        int N = r.channels.size();
-        for (int i = 0; i < N; i++) {
-            final NotificationChannel nc = r.channels.valueAt(i);
-            if (nc.isDeleted()) {
-                deletedCount++;
-            }
-        }
-        return deletedCount;
-    }
-
-    public int getBlockedChannelCount(String pkg, int uid) {
-        Preconditions.checkNotNull(pkg);
-        int blockedCount = 0;
-        Record r = getRecord(pkg, uid);
-        if (r == null) {
-            return blockedCount;
-        }
-        int N = r.channels.size();
-        for (int i = 0; i < N; i++) {
-            final NotificationChannel nc = r.channels.valueAt(i);
-            if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
-                blockedCount++;
-            }
-        }
-        return blockedCount;
-    }
-
-    public int getBlockedAppCount(int userId) {
-        int count = 0;
-        synchronized (mRecords) {
-            final int N = mRecords.size();
-            for (int i = 0; i < N; i++) {
-                final Record r = mRecords.valueAt(i);
-                if (userId == UserHandle.getUserId(r.uid)
-                        && r.importance == IMPORTANCE_NONE) {
-                    count++;
-                }
-            }
-        }
-        return count;
-    }
-
-    public void updateChannelsBypassingDnd() {
-        synchronized (mRecords) {
-            final int numRecords = mRecords.size();
-            for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) {
-                final Record r = mRecords.valueAt(recordIndex);
-                final int numChannels = r.channels.size();
-
-                for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
-                    NotificationChannel channel = r.channels.valueAt(channelIndex);
-                    if (!channel.isDeleted() && channel.canBypassDnd()) {
-                        if (!mAreChannelsBypassingDnd) {
-                            mAreChannelsBypassingDnd = true;
-                            updateZenPolicy(true);
-                        }
-                        return;
-                    }
-                }
-            }
-        }
-
-        if (mAreChannelsBypassingDnd) {
-            mAreChannelsBypassingDnd = false;
-            updateZenPolicy(false);
-        }
-    }
-
-    public void updateZenPolicy(boolean areChannelsBypassingDnd) {
-        NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
-        mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
-                policy.priorityCategories, policy.priorityCallSenders,
-                policy.priorityMessageSenders, policy.suppressedVisualEffects,
-                (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
-                        : 0)));
-    }
-
-    public boolean areChannelsBypassingDnd() {
-        return mAreChannelsBypassingDnd;
-    }
-
-    /**
-     * Sets importance.
-     */
-    @Override
-    public void setImportance(String pkgName, int uid, int importance) {
-        getOrCreateRecord(pkgName, uid).importance = importance;
-        updateConfig();
-    }
-
-    public void setEnabled(String packageName, int uid, boolean enabled) {
-        boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
-        if (wasEnabled == enabled) {
-            return;
-        }
-        setImportance(packageName, uid,
-                enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
-    }
-
-    /**
-     * Sets whether any notifications from the app, represented by the given {@code pkgName} and
-     * {@code uid}, have their importance locked by the user. Locked notifications don't get
-     * considered for sentiment adjustments (and thus never show a blocking helper).
-     */
-    public void setAppImportanceLocked(String packageName, int uid) {
-        Record record = getOrCreateRecord(packageName, uid);
-        if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
-            return;
-        }
-
-        record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
-        updateConfig();
-    }
-
-    @VisibleForTesting
-    void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
-        if (original.canBypassDnd() != update.canBypassDnd()) {
-            update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
-        }
-        if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
-            update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
-        }
-        if (original.getImportance() != update.getImportance()) {
-            update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-        }
-        if (original.shouldShowLights() != update.shouldShowLights()
-                || original.getLightColor() != update.getLightColor()) {
-            update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
-        }
-        if (!Objects.equals(original.getSound(), update.getSound())) {
-            update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
-        }
-        if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
-                || original.shouldVibrate() != update.shouldVibrate()) {
-            update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
-        }
-        if (original.canShowBadge() != update.canShowBadge()) {
-            update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
-        }
-    }
-
     public void dump(PrintWriter pw, String prefix,
             @NonNull NotificationManagerService.DumpFilter filter) {
         final int N = mSignalExtractors.length;
@@ -1088,16 +169,6 @@
             pw.print("  ");
             pw.println(mSignalExtractors[i].getClass().getSimpleName());
         }
-
-        pw.print(prefix);
-        pw.println("per-package config:");
-
-        pw.println("Records:");
-        synchronized (mRecords) {
-            dumpRecords(pw, prefix, filter, mRecords);
-        }
-        pw.println("Restored without uid:");
-        dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
     }
 
     public void dump(ProtoOutputStream proto,
@@ -1105,375 +176,7 @@
         final int N = mSignalExtractors.length;
         for (int i = 0; i < N; i++) {
             proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
-                mSignalExtractors[i].getClass().getSimpleName());
-        }
-        synchronized (mRecords) {
-            dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
-        }
-        dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
-            mRestoredWithoutUids);
-    }
-
-    private static void dumpRecords(ProtoOutputStream proto, long fieldId,
-            @NonNull NotificationManagerService.DumpFilter filter,
-            ArrayMap<String, Record> records) {
-        final int N = records.size();
-        long fToken;
-        for (int i = 0; i < N; i++) {
-            final Record r = records.valueAt(i);
-            if (filter.matches(r.pkg)) {
-                fToken = proto.start(fieldId);
-
-                proto.write(RecordProto.PACKAGE, r.pkg);
-                proto.write(RecordProto.UID, r.uid);
-                proto.write(RecordProto.IMPORTANCE, r.importance);
-                proto.write(RecordProto.PRIORITY, r.priority);
-                proto.write(RecordProto.VISIBILITY, r.visibility);
-                proto.write(RecordProto.SHOW_BADGE, r.showBadge);
-
-                for (NotificationChannel channel : r.channels.values()) {
-                    channel.writeToProto(proto, RecordProto.CHANNELS);
-                }
-                for (NotificationChannelGroup group : r.groups.values()) {
-                    group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
-                }
-
-                proto.end(fToken);
-            }
+                    mSignalExtractors[i].getClass().getSimpleName());
         }
     }
-
-    private static void dumpRecords(PrintWriter pw, String prefix,
-            @NonNull NotificationManagerService.DumpFilter filter,
-            ArrayMap<String, Record> records) {
-        final int N = records.size();
-        for (int i = 0; i < N; i++) {
-            final Record r = records.valueAt(i);
-            if (filter.matches(r.pkg)) {
-                pw.print(prefix);
-                pw.print("  AppSettings: ");
-                pw.print(r.pkg);
-                pw.print(" (");
-                pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
-                pw.print(')');
-                if (r.importance != DEFAULT_IMPORTANCE) {
-                    pw.print(" importance=");
-                    pw.print(Ranking.importanceToString(r.importance));
-                }
-                if (r.priority != DEFAULT_PRIORITY) {
-                    pw.print(" priority=");
-                    pw.print(Notification.priorityToString(r.priority));
-                }
-                if (r.visibility != DEFAULT_VISIBILITY) {
-                    pw.print(" visibility=");
-                    pw.print(Notification.visibilityToString(r.visibility));
-                }
-                pw.print(" showBadge=");
-                pw.print(Boolean.toString(r.showBadge));
-                pw.println();
-                for (NotificationChannel channel : r.channels.values()) {
-                    pw.print(prefix);
-                    pw.print("  ");
-                    pw.print("  ");
-                    pw.println(channel);
-                }
-                for (NotificationChannelGroup group : r.groups.values()) {
-                    pw.print(prefix);
-                    pw.print("  ");
-                    pw.print("  ");
-                    pw.println(group);
-                }
-            }
-        }
-    }
-
-    public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
-        JSONObject ranking = new JSONObject();
-        JSONArray records = new JSONArray();
-        try {
-            ranking.put("noUid", mRestoredWithoutUids.size());
-        } catch (JSONException e) {
-           // pass
-        }
-        synchronized (mRecords) {
-            final int N = mRecords.size();
-            for (int i = 0; i < N; i++) {
-                final Record r = mRecords.valueAt(i);
-                if (filter == null || filter.matches(r.pkg)) {
-                    JSONObject record = new JSONObject();
-                    try {
-                        record.put("userId", UserHandle.getUserId(r.uid));
-                        record.put("packageName", r.pkg);
-                        if (r.importance != DEFAULT_IMPORTANCE) {
-                            record.put("importance", Ranking.importanceToString(r.importance));
-                        }
-                        if (r.priority != DEFAULT_PRIORITY) {
-                            record.put("priority", Notification.priorityToString(r.priority));
-                        }
-                        if (r.visibility != DEFAULT_VISIBILITY) {
-                            record.put("visibility", Notification.visibilityToString(r.visibility));
-                        }
-                        if (r.showBadge != DEFAULT_SHOW_BADGE) {
-                            record.put("showBadge", Boolean.valueOf(r.showBadge));
-                        }
-                        JSONArray channels = new JSONArray();
-                        for (NotificationChannel channel : r.channels.values()) {
-                            channels.put(channel.toJson());
-                        }
-                        record.put("channels", channels);
-                        JSONArray groups = new JSONArray();
-                        for (NotificationChannelGroup group : r.groups.values()) {
-                            groups.put(group.toJson());
-                        }
-                        record.put("groups", groups);
-                    } catch (JSONException e) {
-                        // pass
-                    }
-                    records.put(record);
-                }
-            }
-        }
-        try {
-            ranking.put("records", records);
-        } catch (JSONException e) {
-            // pass
-        }
-        return ranking;
-    }
-
-    /**
-     * Dump only the ban information as structured JSON for the stats collector.
-     *
-     * This is intentionally redundant with {#link dumpJson} because the old
-     * scraper will expect this format.
-     *
-     * @param filter
-     * @return
-     */
-    public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
-        JSONArray bans = new JSONArray();
-        Map<Integer, String> packageBans = getPackageBans();
-        for(Entry<Integer, String> ban : packageBans.entrySet()) {
-            final int userId = UserHandle.getUserId(ban.getKey());
-            final String packageName = ban.getValue();
-            if (filter == null || filter.matches(packageName)) {
-                JSONObject banJson = new JSONObject();
-                try {
-                    banJson.put("userId", userId);
-                    banJson.put("packageName", packageName);
-                } catch (JSONException e) {
-                    e.printStackTrace();
-                }
-                bans.put(banJson);
-            }
-        }
-        return bans;
-    }
-
-    public Map<Integer, String> getPackageBans() {
-        synchronized (mRecords) {
-            final int N = mRecords.size();
-            ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
-            for (int i = 0; i < N; i++) {
-                final Record r = mRecords.valueAt(i);
-                if (r.importance == IMPORTANCE_NONE) {
-                    packageBans.put(r.uid, r.pkg);
-                }
-            }
-
-            return packageBans;
-        }
-    }
-
-    /**
-     * Dump only the channel information as structured JSON for the stats collector.
-     *
-     * This is intentionally redundant with {#link dumpJson} because the old
-     * scraper will expect this format.
-     *
-     * @param filter
-     * @return
-     */
-    public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
-        JSONArray channels = new JSONArray();
-        Map<String, Integer> packageChannels = getPackageChannels();
-        for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
-            final String packageName = channelCount.getKey();
-            if (filter == null || filter.matches(packageName)) {
-                JSONObject channelCountJson = new JSONObject();
-                try {
-                    channelCountJson.put("packageName", packageName);
-                    channelCountJson.put("channelCount", channelCount.getValue());
-                } catch (JSONException e) {
-                    e.printStackTrace();
-                }
-                channels.put(channelCountJson);
-            }
-        }
-        return channels;
-    }
-
-    private Map<String, Integer> getPackageChannels() {
-        ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
-        synchronized (mRecords) {
-            for (int i = 0; i < mRecords.size(); i++) {
-                final Record r = mRecords.valueAt(i);
-                int channelCount = 0;
-                for (int j = 0; j < r.channels.size(); j++) {
-                    if (!r.channels.valueAt(j).isDeleted()) {
-                        channelCount++;
-                    }
-                }
-                packageChannels.put(r.pkg, channelCount);
-            }
-        }
-        return packageChannels;
-    }
-
-    public void onUserRemoved(int userId) {
-        synchronized (mRecords) {
-            int N = mRecords.size();
-            for (int i = N - 1; i >= 0 ; i--) {
-                Record record = mRecords.valueAt(i);
-                if (UserHandle.getUserId(record.uid) == userId) {
-                    mRecords.removeAt(i);
-                }
-            }
-        }
-    }
-
-    protected void onLocaleChanged(Context context, int userId) {
-        synchronized (mRecords) {
-            int N = mRecords.size();
-            for (int i = 0; i < N; i++) {
-                Record record = mRecords.valueAt(i);
-                if (UserHandle.getUserId(record.uid) == userId) {
-                    if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-                        record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
-                                context.getResources().getString(
-                                        R.string.default_notification_channel_label));
-                    }
-                }
-            }
-        }
-    }
-
-    public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
-            int[] uidList) {
-        if (pkgList == null || pkgList.length == 0) {
-            return; // nothing to do
-        }
-        boolean updated = false;
-        if (removingPackage) {
-            // Remove notification settings for uninstalled package
-            int size = Math.min(pkgList.length, uidList.length);
-            for (int i = 0; i < size; i++) {
-                final String pkg = pkgList[i];
-                final int uid = uidList[i];
-                synchronized (mRecords) {
-                    mRecords.remove(recordKey(pkg, uid));
-                }
-                mRestoredWithoutUids.remove(pkg);
-                updated = true;
-            }
-        } else {
-            for (String pkg : pkgList) {
-                // Package install
-                final Record r = mRestoredWithoutUids.get(pkg);
-                if (r != null) {
-                    try {
-                        r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
-                        mRestoredWithoutUids.remove(pkg);
-                        synchronized (mRecords) {
-                            mRecords.put(recordKey(r.pkg, r.uid), r);
-                        }
-                        updated = true;
-                    } catch (NameNotFoundException e) {
-                        // noop
-                    }
-                }
-                // Package upgrade
-                try {
-                    Record fullRecord = getRecord(pkg,
-                            mPm.getPackageUidAsUser(pkg, changeUserId));
-                    if (fullRecord != null) {
-                        createDefaultChannelIfNeeded(fullRecord);
-                        deleteDefaultChannelIfNeeded(fullRecord);
-                    }
-                } catch (NameNotFoundException e) {}
-            }
-        }
-
-        if (updated) {
-            updateConfig();
-        }
-    }
-
-    private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
-        return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
-                .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
-                .setPackageName(pkg)
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
-                        channel.getId())
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
-                        channel.getImportance());
-    }
-
-    private LogMaker getChannelGroupLog(String groupId, String pkg) {
-        return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
-                .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
-                        groupId)
-                .setPackageName(pkg);
-    }
-
-    public void updateBadgingEnabled() {
-        if (mBadgingEnabled == null) {
-            mBadgingEnabled = new SparseBooleanArray();
-        }
-        boolean changed = false;
-        // update the cached values
-        for (int index = 0; index < mBadgingEnabled.size(); index++) {
-            int userId = mBadgingEnabled.keyAt(index);
-            final boolean oldValue = mBadgingEnabled.get(userId);
-            final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
-                    Secure.NOTIFICATION_BADGING,
-                    DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
-            mBadgingEnabled.put(userId, newValue);
-            changed |= oldValue != newValue;
-        }
-        if (changed) {
-            updateConfig();
-        }
-    }
-
-    public boolean badgingEnabled(UserHandle userHandle) {
-        int userId = userHandle.getIdentifier();
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
-        if (mBadgingEnabled.indexOfKey(userId) < 0) {
-            mBadgingEnabled.put(userId,
-                    Secure.getIntForUser(mContext.getContentResolver(),
-                            Secure.NOTIFICATION_BADGING,
-                            DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
-        }
-        return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
-    }
-
-
-    private static class Record {
-        static int UNKNOWN_UID = UserHandle.USER_NULL;
-
-        String pkg;
-        int uid = UNKNOWN_UID;
-        int importance = DEFAULT_IMPORTANCE;
-        int priority = DEFAULT_PRIORITY;
-        int visibility = DEFAULT_VISIBILITY;
-        boolean showBadge = DEFAULT_SHOW_BADGE;
-        int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
-
-        ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
-        Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
-   }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fa934fe..b92d52c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -685,13 +685,14 @@
                 // inserted above to hold the session active.
                 try {
                     final Int64Ref last = new Int64Ref(0);
-                    FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, (long progress) -> {
-                        if (params.sizeBytes > 0) {
-                            final long delta = progress - last.value;
-                            last.value = progress;
-                            addClientProgress((float) delta / (float) params.sizeBytes);
-                        }
-                    }, null, lengthBytes);
+                    FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
+                            Runnable::run, (long progress) -> {
+                                if (params.sizeBytes > 0) {
+                                    final long delta = progress - last.value;
+                                    last.value = progress;
+                                    addClientProgress((float) delta / (float) params.sizeBytes);
+                                }
+                            });
                 } finally {
                     IoUtils.closeQuietly(targetFd);
                     IoUtils.closeQuietly(incomingFd);
@@ -930,6 +931,10 @@
     @GuardedBy("mLock")
     private void commitLocked()
             throws PackageManagerException {
+        if (mRelinquished) {
+            Slog.d(TAG, "Ignoring commit after previous commit relinquished control");
+            return;
+        }
         if (mDestroyed) {
             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 734a872..3d5d079 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2568,6 +2568,8 @@
 
             mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
 
+            int preUpgradeSdkVersion = ver.sdkVersion;
+
             // save off the names of pre-existing system packages prior to scanning; we don't
             // want to automatically grant runtime permissions for new system apps
             if (mPromoteSystemApps) {
@@ -3161,6 +3163,58 @@
 
             checkDefaultBrowser();
 
+            // If a granted permission is split, all new permissions should be granted too
+            if (mIsUpgrade) {
+                final int callingUid = getCallingUid();
+
+                final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+                for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+                    final PackageParser.SplitPermissionInfo splitPerm =
+                            PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+                    final String rootPerm = splitPerm.rootPerm;
+
+                    if (preUpgradeSdkVersion >= splitPerm.targetSdk) {
+                        continue;
+                    }
+
+                    final int numPackages = mPackages.size();
+                    for (int packageNum = 0; packageNum < numPackages; packageNum++) {
+                        final PackageParser.Package pkg = mPackages.valueAt(packageNum);
+
+                        if (pkg.applicationInfo.targetSdkVersion >= splitPerm.targetSdk
+                                || !pkg.requestedPermissions.contains(rootPerm)) {
+                            continue;
+                        }
+
+                        final int userId = UserHandle.getUserId(pkg.applicationInfo.uid);
+                        final String pkgName = pkg.packageName;
+
+                        if (checkPermission(rootPerm, pkgName, userId) == PERMISSION_DENIED) {
+                            continue;
+                        }
+
+                        final String[] newPerms = splitPerm.newPerms;
+
+                        final int numNewPerms = newPerms.length;
+                        for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
+                            final String newPerm = newPerms[newPermNum];
+                            if (checkPermission(newPerm, pkgName, userId) == PERMISSION_GRANTED) {
+                                continue;
+                            }
+
+                            if (DEBUG_PERMISSIONS) {
+                                Slog.v(TAG, "Granting " + newPerm + " to " + pkgName
+                                        + " as the root permission " + rootPerm
+                                        + " is already granted");
+                            }
+
+                            mPermissionManager.grantRuntimePermission(newPerm, pkgName, true,
+                                    callingUid, userId, null);
+                        }
+                    }
+                }
+            }
+
             // clear only after permissions and other defaults have been updated
             mExistingSystemPackages.clear();
             mPromoteSystemApps = false;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 1ae59cb..50e6f8d 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1193,12 +1193,27 @@
         }
     }
 
-    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, boolean ignoreSystemPackage, int userId) {
+    private void grantRuntimePermissions(PackageParser.Package pkg,
+            Set<String> permissionsWithoutSplits, boolean systemFixed, boolean ignoreSystemPackage,
+            int userId) {
         if (pkg.requestedPermissions.isEmpty()) {
             return;
         }
 
+        final ArraySet<String> permissions = new ArraySet<>(permissionsWithoutSplits);
+
+        // Automatically attempt to grant split permissions to older APKs
+        final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+        for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+            final PackageParser.SplitPermissionInfo splitPerm =
+                    PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+
+            if (pkg.applicationInfo.targetSdkVersion < splitPerm.targetSdk
+                    && permissionsWithoutSplits.contains(splitPerm.rootPerm)) {
+                Collections.addAll(permissions, splitPerm.newPerms);
+            }
+        }
+
         List<String> requestedPermissions = pkg.requestedPermissions;
         Set<String> grantablePermissions = null;
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5bc35e7..e6195b4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -290,6 +290,7 @@
 import com.android.server.wm.DisplayFrames;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import com.android.server.wm.utils.InsetUtils;
 
 import java.io.File;
 import java.io.FileReader;
@@ -4536,16 +4537,15 @@
 
     @Override
     // TODO: Should probably be moved into DisplayFrames.
-    public boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets, Rect outStableInsets,
+    public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds,
+            DisplayFrames displayFrames, boolean floatingStack, Rect outFrame,
+            Rect outContentInsets, Rect outStableInsets,
             Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) {
         final int fl = PolicyControl.getWindowFlags(null, attrs);
         final int pfl = attrs.privateFlags;
         final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);
         final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs);
         final int displayRotation = displayFrames.mRotation;
-        final int displayWidth = displayFrames.mDisplayWidth;
-        final int displayHeight = displayFrames.mDisplayHeight;
 
         final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
         if (useOutsets) {
@@ -4569,45 +4569,40 @@
         final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
 
         if (layoutInScreenAndInsetDecor && !screenDecor) {
-            int availRight, availBottom;
             if (canHideNavigationBar() &&
                     (sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
                 outFrame.set(displayFrames.mUnrestricted);
-                availRight = displayFrames.mUnrestricted.right;
-                availBottom = displayFrames.mUnrestricted.bottom;
             } else {
                 outFrame.set(displayFrames.mRestricted);
-                availRight = displayFrames.mRestricted.right;
-                availBottom = displayFrames.mRestricted.bottom;
             }
-            outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top,
-                    availRight - displayFrames.mStable.right,
-                    availBottom - displayFrames.mStable.bottom);
 
-            if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+            final Rect sf;
+            if (floatingStack) {
+                sf = null;
+            } else {
+                sf = displayFrames.mStable;
+            }
+
+            final Rect cf;
+            if (floatingStack) {
+                cf = null;
+            } else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
                 if ((fl & FLAG_FULLSCREEN) != 0) {
-                    outContentInsets.set(displayFrames.mStableFullscreen.left,
-                            displayFrames.mStableFullscreen.top,
-                            availRight - displayFrames.mStableFullscreen.right,
-                            availBottom - displayFrames.mStableFullscreen.bottom);
+                    cf = displayFrames.mStableFullscreen;
                 } else {
-                    outContentInsets.set(outStableInsets);
+                    cf = displayFrames.mStable;
                 }
             } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
-                outContentInsets.setEmpty();
+                cf = displayFrames.mOverscan;
             } else {
-                outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top,
-                        availRight - displayFrames.mCurrent.right,
-                        availBottom - displayFrames.mCurrent.bottom);
+                cf = displayFrames.mCurrent;
             }
 
             if (taskBounds != null) {
-                calculateRelevantTaskInsets(taskBounds, outContentInsets,
-                        displayWidth, displayHeight);
-                calculateRelevantTaskInsets(taskBounds, outStableInsets,
-                        displayWidth, displayHeight);
                 outFrame.intersect(taskBounds);
             }
+            InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets);
+            InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets);
             outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)
                     .getDisplayCutout());
             return mForceShowSystemBars;
@@ -4628,22 +4623,6 @@
         }
     }
 
-    /**
-     * For any given task bounds, the insets relevant for these bounds given the insets relevant
-     * for the entire display.
-     */
-    private void calculateRelevantTaskInsets(Rect taskBounds, Rect inOutInsets, int displayWidth,
-            int displayHeight) {
-        mTmpRect.set(0, 0, displayWidth, displayHeight);
-        mTmpRect.inset(inOutInsets);
-        mTmpRect.intersect(taskBounds);
-        int leftInset = mTmpRect.left - taskBounds.left;
-        int topInset = mTmpRect.top - taskBounds.top;
-        int rightInset = taskBounds.right - mTmpRect.right;
-        int bottomInset = taskBounds.bottom - mTmpRect.bottom;
-        inOutInsets.set(leftInset, topInset, rightInset, bottomInset);
-    }
-
     private boolean shouldUseOutsets(WindowManager.LayoutParams attrs, int fl) {
         return attrs.type == TYPE_WALLPAPER || (fl & (WindowManager.LayoutParams.FLAG_FULLSCREEN
                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN)) != 0;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9fbe419..1ebbe3ac 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -71,7 +71,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -1180,6 +1179,7 @@
      * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
      *                   associated with the window.
      * @param displayFrames display frames.
+     * @param floatingStack Whether the window's stack is floating.
      * @param outFrame The frame of the window.
      * @param outContentInsets The areas covered by system windows, expressed as positive insets.
      * @param outStableInsets The areas covered by stable system windows irrespective of their
@@ -1190,8 +1190,8 @@
      *         See {@link #isNavBarForcedShownLw(WindowState)}.
      */
     default boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets,
-            Rect outStableInsets, Rect outOutsets,
+            DisplayFrames displayFrames, boolean floatingStack,
+            Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
             DisplayCutout.ParcelableWrapper outDisplayCutout) {
         return false;
     }
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4d65440..c7c24a5 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -57,11 +57,14 @@
 import android.os.UserManager;
 import android.telephony.ModemActivityInfo;
 import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelUidCpuTimeReader;
 import com.android.internal.os.KernelUidCpuClusterTimeReader;
@@ -891,6 +894,26 @@
         }
     }
 
+    private void pullBinderCallsStats(int tagId, List<StatsLogEventWrapper> pulledData) {
+        List<ExportedCallStat> callStats = BinderCallsStats.getInstance().getExportedCallStats();
+        long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+        for (ExportedCallStat callStat : callStats) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 11 /* fields */);
+            e.writeInt(callStat.uid);
+            e.writeString(callStat.className);
+            e.writeString(callStat.methodName);
+            e.writeLong(callStat.callCount);
+            e.writeLong(callStat.exceptionCount);
+            e.writeLong(callStat.latencyMicros);
+            e.writeLong(callStat.maxLatencyMicros);
+            e.writeLong(callStat.cpuTimeMicros);
+            e.writeLong(callStat.maxCpuTimeMicros);
+            e.writeLong(callStat.maxReplySizeBytes);
+            e.writeLong(callStat.maxRequestSizeBytes);
+            pulledData.add(e);
+        }
+    }
+
     /**
      * Pulls various data.
      */
@@ -973,6 +996,10 @@
                 pullProcessMemoryState(tagId, ret);
                 break;
             }
+            case StatsLog.BINDER_CALLS: {
+                pullBinderCallsStats(tagId, ret);
+                break;
+            }
             default:
                 Slog.w(TAG, "No such tagId data as " + tagId);
                 return null;
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 2bfff26..cb50460 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -26,6 +26,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.PowerManagerInternal;
 import android.util.ArrayMap;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
@@ -57,6 +59,7 @@
     private final AnimationHandler mAnimationHandler;
     private final Transaction mFrameTransaction;
     private final AnimatorFactory mAnimatorFactory;
+    private final PowerManagerInternal mPowerManagerInternal;
     private boolean mApplyScheduled;
 
     @GuardedBy("mLock")
@@ -70,13 +73,15 @@
     @GuardedBy("mLock")
     private boolean mAnimationStartDeferred;
 
-    SurfaceAnimationRunner() {
-        this(null /* callbackProvider */, null /* animatorFactory */, new Transaction());
+    SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
+        this(null /* callbackProvider */, null /* animatorFactory */, new Transaction(),
+                powerManagerInternal);
     }
 
     @VisibleForTesting
     SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
-            AnimatorFactory animatorFactory, Transaction frameTransaction) {
+            AnimatorFactory animatorFactory, Transaction frameTransaction,
+            PowerManagerInternal powerManagerInternal) {
         SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
                 0 /* timeout */);
         mFrameTransaction = frameTransaction;
@@ -87,6 +92,7 @@
         mAnimatorFactory = animatorFactory != null
                 ? animatorFactory
                 : SfValueAnimator::new;
+        mPowerManagerInternal = powerManagerInternal;
     }
 
     /**
@@ -231,6 +237,7 @@
         synchronized (mLock) {
             startPendingAnimationsLocked();
         }
+        mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
     }
 
     private void scheduleApplyTransaction() {
diff --git a/services/core/java/com/android/server/wm/TEST_MAPPING b/services/core/java/com/android/server/wm/TEST_MAPPING
new file mode 100644
index 0000000..e885afa
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TEST_MAPPING
@@ -0,0 +1,42 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWindowManagerDeviceTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.wm."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsWindowManagerDeviceTestCases"
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.wm."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54703b3..732a828 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1069,7 +1069,7 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
         mHoldingScreenWakeLock.setReferenceCounted(false);
 
-        mSurfaceAnimationRunner = new SurfaceAnimationRunner();
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(mPowerManagerInternal);
 
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
@@ -1469,14 +1469,18 @@
             displayFrames.onDisplayInfoUpdated(displayInfo,
                     displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation));
             final Rect taskBounds;
+            final boolean floatingStack;
             if (atoken != null && atoken.getTask() != null) {
                 taskBounds = mTmpRect;
                 atoken.getTask().getBounds(mTmpRect);
+                floatingStack = atoken.getTask().isFloating();
             } else {
                 taskBounds = null;
+                floatingStack = false;
             }
-            if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, outFrame,
-                    outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
+            if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
+                    outFrame, outContentInsets, outStableInsets, outOutsets,
+                    outDisplayCutout)) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
             }
 
diff --git a/services/core/java/com/android/server/wm/utils/InsetUtils.java b/services/core/java/com/android/server/wm/utils/InsetUtils.java
index b4a998a..c5b103f 100644
--- a/services/core/java/com/android/server/wm/utils/InsetUtils.java
+++ b/services/core/java/com/android/server/wm/utils/InsetUtils.java
@@ -16,8 +16,11 @@
 
 package com.android.server.wm.utils;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Rect;
 
+
 /**
  * Utility methods to handle insets represented as rects.
  */
@@ -35,4 +38,30 @@
         inOutInsets.right += insetsToAdd.right;
         inOutInsets.bottom += insetsToAdd.bottom;
     }
+
+    /**
+     * Calculates the insets from the {@code outerFrame} to the {@code innerFrame}.
+     *
+     * Note that if a side of the outer frame is not actually on the outside, the inset for that
+     * side will be clamped to zero.
+     *
+     * @param outerFrame the reference frame from which the insets are calculated
+     * @param innerFrame the inset frame, to which the insets are calculated,
+     *                   or null to clear the insets.
+     * @param outInsets is set to the result of the inset calculation.
+     */
+    public static void insetsBetweenFrames(@NonNull Rect outerFrame, @Nullable Rect innerFrame,
+            @NonNull Rect outInsets) {
+        if (innerFrame == null) {
+            outInsets.setEmpty();
+            return;
+        }
+        final int w = outerFrame.width();
+        final int h = outerFrame.height();
+        outInsets.set(
+                Math.min(w, Math.max(0, innerFrame.left - outerFrame.left)),
+                Math.min(h, Math.max(0, innerFrame.top - outerFrame.top)),
+                Math.min(w, Math.max(0, outerFrame.right - innerFrame.right)),
+                Math.min(h, Math.max(0, outerFrame.bottom - innerFrame.bottom)));
+    }
 }
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 4fbc14c..e8266a5 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -243,7 +243,7 @@
             intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
             intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
             intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
-            intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
 
             IntentSender intentSender = PendingIntent.getActivityAsUser(
                     mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
new file mode 100644
index 0000000..865050fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 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.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertEquals;
+
+import android.media.AudioManager;
+import android.support.test.filters.SmallTest;
+import junit.framework.Assert;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/**
+ * Tests for {@link HdmiCecLocalDeviceAudioSystem} class.
+ */
+public class HdmiCecLocalDeviceAudioSystemTest {
+
+
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+    private HdmiCecMessage mResultMessage;
+    private int mMusicVolume;
+    private int mMusicMaxVolume;
+    private boolean mMusicMute;
+
+    @Before
+    public void SetUp() {
+        mHdmiControlService = new HdmiControlService(null) {
+            @Override
+            AudioManager getAudioManager() {
+                return new AudioManager() {
+                    @Override
+                    public int getStreamVolume(int streamType) {
+                        switch (streamType) {
+                            case STREAM_MUSIC:
+                                return mMusicVolume;
+                            default:
+                                return 0;
+                        }
+                    }
+
+                    @Override
+                    public boolean isStreamMute(int streamType) {
+                        switch (streamType) {
+                            case STREAM_MUSIC:
+                                return mMusicMute;
+                            default:
+                                return false;
+                        }
+                    }
+
+                    @Override
+                    public int getStreamMaxVolume(int streamType) {
+                        switch (streamType) {
+                            case STREAM_MUSIC:
+                                return mMusicMaxVolume;
+                            default:
+                                return 100;
+                        }
+                    }
+                };
+            }
+
+            @Override
+            void sendCecCommand(HdmiCecMessage command) {
+                mResultMessage = command;
+            }
+        };
+        mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+    }
+
+    @Test
+    public void handleGiveAudioStatus_volume_10_mute_true() {
+        mMusicVolume = 10;
+        mMusicMute = true;
+        mMusicMaxVolume = 20;
+        int scaledVolume = VolumeControlAction.scaleToCecVolume(10, mMusicMaxVolume);
+        HdmiCecMessage expectMessage = HdmiCecMessageBuilder.buildReportAudioStatus(
+            ADDR_UNREGISTERED, ADDR_TV, scaledVolume, true);
+
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildGiveAudioStatus(
+            ADDR_TV, ADDR_AUDIO_SYSTEM);
+        assertTrue(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(message));
+
+        assertTrue(mResultMessage.equals(expectMessage));
+    }
+
+    @Test
+    public void handleGiveSystemAudioModeStatus_off() {
+        HdmiCecMessage expectMessage = HdmiCecMessageBuilder
+            .buildReportSystemAudioMode(ADDR_UNREGISTERED, ADDR_TV, false);
+
+        HdmiCecMessage message = HdmiCecMessageBuilder
+            .buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
+        assertTrue(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(message));
+
+        assertTrue(mResultMessage.equals(expectMessage));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 97a716f..cb94ec7 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -22,7 +22,6 @@
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -309,8 +308,8 @@
         final Rect stable = new Rect();
         final Rect outsets = new Rect();
         final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper();
-        mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content,
-                stable, outsets, cutout);
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames,
+                false /* floatingStack */, frame, content, stable, outsets, cutout);
 
         assertThat(frame, equalTo(mFrames.mUnrestricted));
         assertThat(content, equalTo(new Rect()));
@@ -331,8 +330,8 @@
         final DisplayCutout.ParcelableWrapper outDisplayCutout =
                 new DisplayCutout.ParcelableWrapper();
 
-        mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets,
-                outStableInsets, outOutsets, outDisplayCutout);
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, false /* floatingStack */,
+                outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
 
         assertThat(outFrame, is(mFrames.mUnrestricted));
         assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT)));
@@ -355,8 +354,35 @@
         final DisplayCutout.ParcelableWrapper outDisplayCutout =
                 new DisplayCutout.ParcelableWrapper();
 
-        mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets,
-                outStableInsets, outOutsets, outDisplayCutout);
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, false /* floatingStack */,
+                outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
+
+        assertThat(outFrame, is(taskBounds));
+        assertThat(outContentInsets, is(new Rect()));
+        assertThat(outStableInsets, is(new Rect()));
+        assertThat(outOutsets, is(new Rect()));
+        assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
+    }
+
+    @Test
+    public void layoutHint_appWindowInTask_outsideContentFrame() {
+        // Initialize DisplayFrames
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+
+        // Task is in the nav bar area (usually does not happen, but this is similar enough to the
+        // possible overlap with the IME)
+        final Rect taskBounds = new Rect(100, mFrames.mContent.bottom + 1,
+                200, mFrames.mContent.bottom + 10);
+
+        final Rect outFrame = new Rect();
+        final Rect outContentInsets = new Rect();
+        final Rect outStableInsets = new Rect();
+        final Rect outOutsets = new Rect();
+        final DisplayCutout.ParcelableWrapper outDisplayCutout =
+                new DisplayCutout.ParcelableWrapper();
+
+        mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, true /* floatingStack */,
+                outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
 
         assertThat(outFrame, is(taskBounds));
         assertThat(outContentInsets, is(new Rect()));
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index edac8a5..79e9bb4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -20,24 +20,23 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.animation.AnimationHandler;
 import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
 import android.animation.ValueAnimator;
 import android.graphics.Matrix;
 import android.graphics.Point;
+import android.os.PowerManagerInternal;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
 import android.view.Choreographer;
 import android.view.Choreographer.FrameCallback;
 import android.view.SurfaceControl;
@@ -46,7 +45,6 @@
 import android.view.animation.TranslateAnimation;
 
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-import com.android.server.wm.SurfaceAnimationRunner.AnimatorFactory;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -71,6 +69,7 @@
     @Mock SurfaceControl mMockSurface;
     @Mock Transaction mMockTransaction;
     @Mock AnimationSpec mMockAnimationSpec;
+    @Mock PowerManagerInternal mMockPowerManager;
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     private SurfaceAnimationRunner mSurfaceAnimationRunner;
@@ -81,7 +80,7 @@
         super.setUp();
         mFinishCallbackLatch = new CountDownLatch(1);
         mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
-                mMockTransaction);
+                mMockTransaction, mMockPowerManager);
     }
 
     private void finishedCallback() {
@@ -113,7 +112,7 @@
     @Test
     public void testCancel_notStarted() throws Exception {
         mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
-                mMockTransaction);
+                mMockTransaction, mMockPowerManager);
         mSurfaceAnimationRunner
                 .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
                 this::finishedCallback);
@@ -126,7 +125,7 @@
     @Test
     public void testCancel_running() throws Exception {
         mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
-                mMockTransaction);
+                mMockTransaction, mMockPowerManager);
         mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
                 mMockTransaction, this::finishedCallback);
         waitUntilNextFrame();
@@ -156,7 +155,7 @@
                     listener.onAnimationUpdate(animation);
                 });
             }
-        }, mMockTransaction);
+        }, mMockTransaction, mMockPowerManager);
         when(mMockAnimationSpec.getDuration()).thenReturn(200L);
         mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
                 this::finishedCallback);
@@ -184,6 +183,19 @@
         assertFinishCallbackCalled();
     }
 
+    @Test
+    public void testPowerHint() throws Exception {
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
+                mMockTransaction, mMockPowerManager);
+        mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
+                mMockTransaction, this::finishedCallback);
+        waitUntilNextFrame();
+
+        // TODO: For some reason we don't have access to PowerHint definition from the tests. For
+        // now let's just verify that we got some kind of hint.
+        verify(mMockPowerManager).powerHint(anyInt(), anyInt());
+    }
+
     private void waitUntilNextFrame() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index fd674f0..f17a30d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -25,6 +25,9 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.Adjustment;
@@ -54,6 +57,9 @@
         ArrayList<String> people = new ArrayList<>();
         people.add("you");
         signals.putStringArrayList(Adjustment.KEY_PEOPLE, people);
+        ArrayList<Notification.Action> smartActions = new ArrayList<>();
+        smartActions.add(createAction());
+        signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
         Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
         r.addAdjustment(adjustment);
 
@@ -66,6 +72,7 @@
         assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
         assertEquals(people, r.getPeopleOverride());
         assertEquals(snoozeCriteria, r.getSnoozeCriteria());
+        assertEquals(smartActions, r.getSmartActions());
     }
 
     @Test
@@ -114,4 +121,11 @@
                 0, n, UserHandle.ALL, null, System.currentTimeMillis());
        return new NotificationRecord(getContext(), sbn, channel);
     }
+
+    private Notification.Action createAction() {
+        return new Notification.Action.Builder(
+                Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+                "action",
+                PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index ef9ba78..742ad65 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -31,11 +31,14 @@
 import static org.mockito.Mockito.when;
 
 import android.app.INotificationManager;
+import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationRankingUpdate;
@@ -91,6 +94,7 @@
             assertEquals(getShowBadge(i), ranking.canShowBadge());
             assertEquals(getUserSentiment(i), ranking.getUserSentiment());
             assertEquals(getHidden(i), ranking.isSuspended());
+            assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
         }
     }
 
@@ -107,6 +111,7 @@
         int[] importance = new int[mKeys.length];
         Bundle userSentiment = new Bundle();
         Bundle mHidden = new Bundle();
+        Bundle smartActions = new Bundle();
 
         for (int i = 0; i < mKeys.length; i++) {
             String key = mKeys[i];
@@ -124,11 +129,13 @@
             showBadge.putBoolean(key, getShowBadge(i));
             userSentiment.putInt(key, getUserSentiment(i));
             mHidden.putBoolean(key, getHidden(i));
+            smartActions.putParcelableArrayList(key, getSmartActions(key, i));
         }
         NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
                 interceptedKeys.toArray(new String[0]), visibilityOverrides,
                 suppressedVisualEffects, importance, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden);
+                channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden,
+                smartActions);
         return update;
     }
 
@@ -196,6 +203,29 @@
         return snooze;
     }
 
+    private ArrayList<Notification.Action> getSmartActions(String key, int index) {
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        for (int i = 0; i < index; i++) {
+            PendingIntent intent = PendingIntent.getBroadcast(
+                    getContext(),
+                    index /*requestCode*/,
+                    new Intent("ACTION_" + key),
+                    0 /*flags*/);
+            actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build());
+        }
+        return actions;
+    }
+
+    private void assertActionsEqual(
+            List<Notification.Action> expecteds, List<Notification.Action> actuals) {
+        assertEquals(expecteds.size(), actuals.size());
+        for (int i = 0; i < expecteds.size(); i++) {
+            Notification.Action expected = expecteds.get(i);
+            Notification.Action actual = actuals.get(i);
+            assertEquals(expected.title, actual.title);
+        }
+    }
+
     public static class TestListenerService extends NotificationListenerService {
         private final IBinder binder = new LocalBinder();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 45a3c41..e7a8b58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -158,6 +158,7 @@
     private TestableLooper mTestableLooper;
     @Mock
     private RankingHelper mRankingHelper;
+    @Mock private PreferencesHelper mPreferencesHelper;
     AtomicFile mPolicyFile;
     File mFile;
     @Mock
@@ -600,8 +601,8 @@
     @Test
     public void testBlockedNotifications_blockedChannelGroup() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
 
         NotificationChannel channel = new NotificationChannel("id", "name",
                 NotificationManager.IMPORTANCE_HIGH);
@@ -1222,36 +1223,36 @@
     @Test
     public void testTvExtenderChannelOverride_onTv() throws Exception {
         mService.setIsTelevision(true);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
                         new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
-        verify(mRankingHelper, times(1)).getNotificationChannel(
+        verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean());
     }
 
     @Test
     public void testTvExtenderChannelOverride_notOnTv() throws Exception {
         mService.setIsTelevision(false);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
                 mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
-        verify(mRankingHelper, times(1)).getNotificationChannel(
+        verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
     }
 
     @Test
     public void testUpdateAppNotifyCreatorBlock() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
 
         mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -1265,7 +1266,7 @@
 
     @Test
     public void testUpdateAppNotifyCreatorUnblock() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
 
         mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -1279,8 +1280,8 @@
 
     @Test
     public void testUpdateChannelNotifyCreatorBlock() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
 
@@ -1305,8 +1306,8 @@
         NotificationChannel existingChannel =
                 new NotificationChannel(mTestNotificationChannel.getId(),
                         mTestNotificationChannel.getName(), IMPORTANCE_NONE);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(existingChannel);
 
@@ -1327,8 +1328,8 @@
         NotificationChannel existingChannel =
                 new NotificationChannel(mTestNotificationChannel.getId(),
                         mTestNotificationChannel.getName(), IMPORTANCE_MAX);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(existingChannel);
 
@@ -1339,8 +1340,8 @@
     @Test
     public void testUpdateGroupNotifyCreatorBlock() throws Exception {
         NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
                 .thenReturn(existing);
 
         NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
@@ -1362,8 +1363,8 @@
     public void testUpdateGroupNotifyCreatorUnblock() throws Exception {
         NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
         existing.setBlocked(true);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
                 .thenReturn(existing);
 
         mBinderService.updateNotificationChannelGroupForPackage(
@@ -1382,8 +1383,8 @@
     @Test
     public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
         NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
                 .thenReturn(existing);
 
         mBinderService.updateNotificationChannelGroupForPackage(
@@ -1396,12 +1397,12 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
         NotificationChannel channel2 = new NotificationChannel("a", "b", IMPORTANCE_LOW);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(channel2.getId()), anyBoolean()))
                 .thenReturn(channel2);
 
@@ -1421,7 +1422,7 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
         NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m");
 
@@ -1441,9 +1442,9 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         mTestNotificationChannel.setLightColor(Color.CYAN);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
 
@@ -1459,8 +1460,8 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
         reset(mListeners);
@@ -1476,8 +1477,8 @@
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
         NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
-        mService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
                 .thenReturn(ncg);
         reset(mListeners);
         mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId());
@@ -1488,18 +1489,18 @@
 
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
 
         mBinderService.updateNotificationChannelFromPrivilegedListener(
                 null, PKG, Process.myUserHandle(), mTestNotificationChannel);
 
-        verify(mRankingHelper, times(1)).updateNotificationChannel(
+        verify(mPreferencesHelper, times(1)).updateNotificationChannel(
                 anyString(), anyInt(), any(), anyBoolean());
 
         verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1509,7 +1510,7 @@
 
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
 
@@ -1521,7 +1522,7 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).updateNotificationChannel(
+        verify(mPreferencesHelper, never()).updateNotificationChannel(
                 anyString(), anyInt(), any(), anyBoolean());
 
         verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1531,7 +1532,7 @@
 
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1548,7 +1549,7 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).updateNotificationChannel(
+        verify(mPreferencesHelper, never()).updateNotificationChannel(
                 anyString(), anyInt(), any(), anyBoolean());
 
         verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1558,7 +1559,7 @@
 
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_success() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1566,13 +1567,13 @@
         mBinderService.getNotificationChannelsFromPrivilegedListener(
                 null, PKG, Process.myUserHandle());
 
-        verify(mRankingHelper, times(1)).getNotificationChannels(
+        verify(mPreferencesHelper, times(1)).getNotificationChannels(
                 anyString(), anyInt(), anyBoolean());
     }
 
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
 
@@ -1584,13 +1585,13 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).getNotificationChannels(
+        verify(mPreferencesHelper, never()).getNotificationChannels(
                 anyString(), anyInt(), anyBoolean());
     }
 
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1606,13 +1607,13 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).getNotificationChannels(
+        verify(mPreferencesHelper, never()).getNotificationChannels(
                 anyString(), anyInt(), anyBoolean());
     }
 
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1620,12 +1621,12 @@
         mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
                 null, PKG, Process.myUserHandle());
 
-        verify(mRankingHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt());
+        verify(mPreferencesHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt());
     }
 
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
 
@@ -1637,12 +1638,12 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
+        verify(mPreferencesHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
     }
 
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
@@ -1657,7 +1658,7 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
+        verify(mPreferencesHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
     }
 
     @Test
@@ -2182,7 +2183,7 @@
 
     @Test
     public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception {
-        mService.setRankingHelper(mRankingHelper);
+        mService.setPreferencesHelper(mPreferencesHelper);
         NotificationManagerService.WorkerHandler handler = mock(
                 NotificationManagerService.WorkerHandler.class);
         mService.setHandler(handler);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index e286991..bd6416d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -34,8 +34,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -48,6 +46,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.Color;
+import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.metrics.LogMaker;
 import android.net.Uri;
@@ -59,8 +58,8 @@
 import android.service.notification.StatusBarNotification;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiServiceTestCase;
 
@@ -70,6 +69,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.Objects;
 
 @SmallTest
@@ -697,4 +697,20 @@
         record.calculateGrantableUris();
         // should not throw
     }
+
+    @Test
+    public void testSmartActions() {
+        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertNull(record.getSmartActions());
+
+        ArrayList<Notification.Action> smartActions = new ArrayList<>();
+        smartActions.add(new Notification.Action.Builder(
+                Icon.createWithResource(getContext(), R.drawable.btn_default),
+                "text", null).build());
+        record.setSmartActions(smartActions);
+        assertEquals(smartActions, record.getSmartActions());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
new file mode 100644
index 0000000..02d5869
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -0,0 +1,1742 @@
+/*
+ * Copyright (C) 2018 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.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContentResolver;
+import android.util.ArrayMap;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.UiServiceTestCase;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferencesHelperTest extends UiServiceTestCase {
+    private static final String PKG = "com.android.server.notification";
+    private static final int UID = 0;
+    private static final UserHandle USER = UserHandle.of(0);
+    private static final String UPDATED_PKG = "updatedPkg";
+    private static final int UID2 = 1111;
+    private static final String SYSTEM_PKG = "android";
+    private static final int SYSTEM_UID= 1000;
+    private static final UserHandle USER2 = UserHandle.of(10);
+    private static final String TEST_CHANNEL_ID = "test_channel_id";
+    private static final String TEST_AUTHORITY = "test";
+    private static final Uri SOUND_URI =
+            Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
+    private static final Uri CANONICAL_SOUND_URI =
+            Uri.parse("content://" + TEST_AUTHORITY
+                    + "/internal/audio/media/10?title=Test&canonical=1");
+
+    @Mock NotificationUsageStats mUsageStats;
+    @Mock RankingHandler mHandler;
+    @Mock PackageManager mPm;
+    @Mock IContentProvider mTestIContentProvider;
+    @Mock Context mContext;
+    @Mock ZenModeHelper mMockZenModeHelper;
+
+    private NotificationManager.Policy mTestNotificationPolicy;
+
+    private PreferencesHelper mHelper;
+    private AudioAttributes mAudioAttributes;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        UserHandle user = UserHandle.ALL;
+
+        final ApplicationInfo legacy = new ApplicationInfo();
+        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+        final ApplicationInfo upgrade = new ApplicationInfo();
+        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+        when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+        when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+        when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
+        when(mPm.getPackageUidAsUser(eq(UPDATED_PKG), anyInt())).thenReturn(UID2);
+        when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
+        PackageInfo info = mock(PackageInfo.class);
+        info.signatures = new Signature[] {mock(Signature.class)};
+        when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info);
+        when(mPm.getPackageInfoAsUser(eq(PKG), anyInt(), anyInt()))
+                .thenReturn(mock(PackageInfo.class));
+        when(mContext.getResources()).thenReturn(
+                InstrumentationRegistry.getContext().getResources());
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getContext().getContentResolver());
+        when(mContext.getPackageManager()).thenReturn(mPm);
+        when(mContext.getApplicationInfo()).thenReturn(legacy);
+        // most tests assume badging is enabled
+        TestableContentResolver contentResolver = getContext().getContentResolver();
+        contentResolver.setFallbackToExisting(false);
+        Secure.putIntForUser(contentResolver,
+                Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID));
+
+        ContentProvider testContentProvider = mock(ContentProvider.class);
+        when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
+        contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
+
+        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+                .thenReturn(CANONICAL_SOUND_URI);
+        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(CANONICAL_SOUND_URI);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(SOUND_URI);
+
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        resetZenModeHelper();
+
+        mAudioAttributes = new AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                .build();
+    }
+
+    private NotificationChannel getDefaultChannel() {
+        return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
+                IMPORTANCE_LOW);
+    }
+
+    private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
+            String... channelIds)
+            throws Exception {
+        XmlSerializer serializer = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mHelper.writeXml(serializer, forBackup);
+        serializer.endDocument();
+        serializer.flush();
+        for (String channelId : channelIds) {
+            mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
+        }
+        return baos;
+    }
+
+    private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
+        loadByteArrayXml(stream.toByteArray(), forRestore);
+    }
+
+    private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+        parser.nextTag();
+        mHelper.readXml(parser, forRestore);
+    }
+
+    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getName(), actual.getName());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
+        assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
+        assertEquals(expected.getImportance(), actual.getImportance());
+        assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
+        assertEquals(expected.getSound(), actual.getSound());
+        assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
+        assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(expected.getGroup(), actual.getGroup());
+        assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
+        assertEquals(expected.getLightColor(), actual.getLightColor());
+    }
+
+    private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getName(), actual.getName());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.isBlocked(), actual.isBlocked());
+    }
+
+    private NotificationChannel getChannel() {
+        return new NotificationChannel("id", "name", IMPORTANCE_LOW);
+    }
+
+    private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
+        for (NotificationChannel channel : channels) {
+            if (channel.getId().equals(id)) {
+                return channel;
+            }
+        }
+        return null;
+    }
+
+    private void resetZenModeHelper() {
+        reset(mMockZenModeHelper);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+    }
+
+    @Test
+    public void testChannelXml() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+        ncg.setBlocked(true);
+        ncg.setDescription("group desc");
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel2.setDescription("descriptions for all");
+        channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel2.enableLights(true);
+        channel2.setBypassDnd(true);
+        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.enableVibration(true);
+        channel2.setGroup(ncg.getId());
+        channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
+        channel2.setLightColor(Color.BLUE);
+
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+
+        mHelper.setShowBadge(PKG, UID, true);
+        mHelper.setAppImportanceLocked(PKG, UID);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
+                channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
+
+        loadStreamXml(baos, false);
+
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
+        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+        compareChannels(channel2,
+                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+
+        List<NotificationChannelGroup> actualGroups =
+                mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
+        boolean foundNcg = false;
+        for (NotificationChannelGroup actual : actualGroups) {
+            if (ncg.getId().equals(actual.getId())) {
+                foundNcg = true;
+                compareGroups(ncg, actual);
+            } else if (ncg2.getId().equals(actual.getId())) {
+                compareGroups(ncg2, actual);
+            }
+        }
+        assertTrue(foundNcg);
+
+        boolean foundChannel2Group = false;
+        for (NotificationChannelGroup actual : actualGroups) {
+            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
+                foundChannel2Group = true;
+                break;
+            }
+        }
+        assertTrue(foundChannel2Group);
+    }
+
+    @Test
+    public void testChannelXmlForBackup() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel2.setDescription("descriptions for all");
+        channel2.setSound(SOUND_URI, mAudioAttributes);
+        channel2.enableLights(true);
+        channel2.setBypassDnd(true);
+        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.enableVibration(false);
+        channel2.setGroup(ncg.getId());
+        channel2.setLightColor(Color.BLUE);
+        NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+        channel3.enableVibration(true);
+
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+        mHelper.createNotificationChannel(PKG, UID, channel3, false, false);
+        mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
+
+        mHelper.setShowBadge(PKG, UID, true);
+
+        mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
+                channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG},
+                new int[]{UID, UID2});
+
+        mHelper.setShowBadge(UPDATED_PKG, UID2, true);
+
+        loadStreamXml(baos, true);
+
+        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+        compareChannels(channel2,
+                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+        compareChannels(channel3,
+                mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+
+        List<NotificationChannelGroup> actualGroups =
+                mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
+        boolean foundNcg = false;
+        for (NotificationChannelGroup actual : actualGroups) {
+            if (ncg.getId().equals(actual.getId())) {
+                foundNcg = true;
+                compareGroups(ncg, actual);
+            } else if (ncg2.getId().equals(actual.getId())) {
+                compareGroups(ncg2, actual);
+            }
+        }
+        assertTrue(foundNcg);
+
+        boolean foundChannel2Group = false;
+        for (NotificationChannelGroup actual : actualGroups) {
+            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
+                foundChannel2Group = true;
+                break;
+            }
+        }
+        assertTrue(foundChannel2Group);
+    }
+
+    @Test
+    public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        // Testing that in restore we are given the canonical version
+        loadStreamXml(baos, true);
+        verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
+    }
+
+    @Test
+    public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
+        Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
+        Uri canonicalBasedOnLocal = localUri.buildUpon()
+                .appendQueryParameter("title", "Test")
+                .appendQueryParameter("canonical", "1")
+                .build();
+        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(canonicalBasedOnLocal);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(localUri);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
+                .thenReturn(localUri);
+
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        loadStreamXml(baos, true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false);
+        assertEquals(localUri, actualChannel.getSound());
+    }
+
+    @Test
+    public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
+        Thread.sleep(3000);
+        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(null);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(null);
+
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        loadStreamXml(baos, true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+    }
+
+
+    /**
+     * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
+     * handle its restore properly.
+     */
+    @Test
+    public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
+        // Not a local uncanonicalized uri, simulating that it fails to exist locally
+        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
+        String id = "id";
+        String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+                + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
+                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+                + "sound=\"" + SOUND_URI + "\" "
+                + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
+                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+
+        loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+    }
+
+    @Test
+    public void testBackupRestoreXml_withNullSoundUri() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(null, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        loadStreamXml(baos, true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false);
+        assertEquals(null, actualChannel.getSound());
+    }
+
+    @Test
+    public void testChannelXml_backup() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        NotificationChannel channel3 =
+                new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
+        channel3.setGroup(ncg.getId());
+
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+        mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+        mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
+                channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, true);
+
+        assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+        //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+    }
+
+    @Test
+    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+                NotificationChannel.DEFAULT_CHANNEL_ID);
+
+        loadStreamXml(baos, false);
+
+        final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
+        assertFalse(updated.canBypassDnd());
+        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
+        assertEquals(0, updated.getUserLockedFields());
+    }
+
+    @Test
+    public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+        mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+                NotificationChannel.DEFAULT_CHANNEL_ID);
+
+        loadStreamXml(baos, false);
+
+        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+    }
+
+    @Test
+    public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
+        final String preupgradeXml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG
+                + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
+                + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
+                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+                + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
+                + Notification.VISIBILITY_PRIVATE + "\" />\n"
+                + "</ranking>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false);
+
+        final NotificationChannel updated1 =
+            mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
+        assertTrue(updated1.canBypassDnd());
+        assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
+        assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
+                | NotificationChannel.USER_LOCKED_PRIORITY
+                | NotificationChannel.USER_LOCKED_VISIBILITY,
+                updated1.getUserLockedFields());
+
+        // No Default Channel created for updated packages
+        assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false));
+    }
+
+    @Test
+    public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        assertTrue(defaultChannel != null);
+        ByteArrayOutputStream baos =
+                writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+        // Load package at higher sdk.
+        final ApplicationInfo upgraded = new ApplicationInfo();
+        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+        loadStreamXml(baos, false);
+
+        // Default Channel should be gone.
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false));
+    }
+
+    @Test
+    public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+                NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+        // Load package at higher sdk.
+        final ApplicationInfo upgraded = new ApplicationInfo();
+        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+        loadStreamXml(baos, false);
+
+        // Default Channel should be gone.
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false));
+    }
+
+    @Test
+    public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+                NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+
+        loadStreamXml(baos, false);
+
+        // Should still have the newly created channel that wasn't in the xml.
+        assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
+    }
+
+    @Test
+    public void testCreateChannel_blocked() throws Exception {
+        mHelper.setImportance(PKG, UID, IMPORTANCE_NONE);
+
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+    }
+
+    @Test
+    public void testCreateChannel_badImportance() throws Exception {
+        try {
+            mHelper.createNotificationChannel(PKG, UID,
+                    new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
+                    true, false);
+            fail("Was allowed to create a channel with invalid importance");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
+        try {
+            mHelper.createNotificationChannel(PKG, UID,
+                    new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
+                    true, false);
+            fail("Was allowed to create a channel with invalid importance");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
+        try {
+            mHelper.createNotificationChannel(PKG, UID,
+                    new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
+                    true, false);
+            fail("Was allowed to create a channel with invalid importance");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
+    }
+
+
+    @Test
+    public void testUpdate() throws Exception {
+        // no fields locked by user
+        final NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel.enableLights(true);
+        channel.setBypassDnd(true);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+        mHelper.createNotificationChannel(PKG, UID, channel, false, false);
+
+        // same id, try to update all fields
+        final NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+        channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
+        channel2.enableLights(false);
+        channel2.setBypassDnd(false);
+        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+        mHelper.updateNotificationChannel(PKG, UID, channel2, true);
+
+        // all fields should be changed
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
+
+        verify(mHandler, times(1)).requestSort();
+    }
+
+    @Test
+    public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
+        mHelper.setImportance(PKG, UID, IMPORTANCE_UNSPECIFIED);
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+                mHelper.getPackageVisibility(PKG, UID));
+        assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
+
+        NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+
+        defaultChannel.setShowBadge(false);
+        defaultChannel.setImportance(IMPORTANCE_NONE);
+        defaultChannel.setBypassDnd(true);
+        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+        mHelper.setAppImportanceLocked(PKG, UID);
+        mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
+
+        // ensure app level fields are changed
+        assertFalse(mHelper.canShowBadge(PKG, UID));
+        assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
+        assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
+        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
+        assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
+    }
+
+    @Test
+    public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
+        final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+
+        mHelper.createNotificationChannel(PKG, UID, channel, false, false);
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+                mHelper.getPackageVisibility(PKG, UID));
+
+        channel.setShowBadge(false);
+        channel.setImportance(IMPORTANCE_NONE);
+        channel.setBypassDnd(true);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+        mHelper.updateNotificationChannel(PKG, UID, channel, true);
+
+        // ensure app level fields are not changed
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+                mHelper.getPackageVisibility(PKG, UID));
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+    }
+
+    @Test
+    public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
+    }
+
+    @Test
+    public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
+        final NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel.enableLights(true);
+        channel.setBypassDnd(true);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setShowBadge(true);
+        int lockMask = 0;
+        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+        }
+        channel.lockFields(lockMask);
+
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+        NotificationChannel savedChannel =
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+
+        assertEquals(channel.getName(), savedChannel.getName());
+        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+        assertFalse(savedChannel.canBypassDnd());
+        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
+
+        verify(mHandler, never()).requestSort();
+    }
+
+    @Test
+    public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
+        final NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel.enableLights(true);
+        channel.setBypassDnd(true);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setShowBadge(true);
+        int lockMask = 0;
+        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+        }
+        channel.lockFields(lockMask);
+
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+        NotificationChannel savedChannel =
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+
+        assertEquals(channel.getName(), savedChannel.getName());
+        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+        assertFalse(savedChannel.canBypassDnd());
+        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
+    }
+
+    @Test
+    public void testClearLockedFields() throws Exception {
+        final NotificationChannel channel = getChannel();
+        mHelper.clearLockedFields(channel);
+        assertEquals(0, channel.getUserLockedFields());
+
+        channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
+                | NotificationChannel.USER_LOCKED_IMPORTANCE);
+        mHelper.clearLockedFields(channel);
+        assertEquals(0, channel.getUserLockedFields());
+    }
+
+    @Test
+    public void testLockFields_soundAndVibration() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+        final NotificationChannel update1 = getChannel();
+        update1.setSound(new Uri.Builder().scheme("test").build(),
+                new AudioAttributes.Builder().build());
+        update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
+        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+                | NotificationChannel.USER_LOCKED_SOUND,
+                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+                        .getUserLockedFields());
+
+        NotificationChannel update2 = getChannel();
+        update2.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
+        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+                        | NotificationChannel.USER_LOCKED_SOUND
+                        | NotificationChannel.USER_LOCKED_VIBRATION,
+                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+                        .getUserLockedFields());
+    }
+
+    @Test
+    public void testLockFields_vibrationAndLights() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+        final NotificationChannel update1 = getChannel();
+        update1.setVibrationPattern(new long[]{7945, 46 ,246});
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
+        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
+                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+                        .getUserLockedFields());
+
+        final NotificationChannel update2 = getChannel();
+        update2.enableLights(true);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
+        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
+                        | NotificationChannel.USER_LOCKED_LIGHTS,
+                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+                        .getUserLockedFields());
+    }
+
+    @Test
+    public void testLockFields_lightsAndImportance() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+        final NotificationChannel update1 = getChannel();
+        update1.setLightColor(Color.GREEN);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
+        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
+                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+                        .getUserLockedFields());
+
+        final NotificationChannel update2 = getChannel();
+        update2.setImportance(IMPORTANCE_DEFAULT);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
+        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
+                        | NotificationChannel.USER_LOCKED_IMPORTANCE,
+                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+                        .getUserLockedFields());
+    }
+
+    @Test
+    public void testLockFields_visibilityAndDndAndBadge() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+        assertEquals(0,
+                mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
+                        .getUserLockedFields());
+
+        final NotificationChannel update1 = getChannel();
+        update1.setBypassDnd(true);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
+        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
+                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+                        .getUserLockedFields());
+
+        final NotificationChannel update2 = getChannel();
+        update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
+        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+                        | NotificationChannel.USER_LOCKED_VISIBILITY,
+                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+                        .getUserLockedFields());
+
+        final NotificationChannel update3 = getChannel();
+        update3.setShowBadge(false);
+        mHelper.updateNotificationChannel(PKG, UID, update3, true);
+        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+                        | NotificationChannel.USER_LOCKED_VISIBILITY
+                        | NotificationChannel.USER_LOCKED_SHOW_BADGE,
+                mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
+                        .getUserLockedFields());
+    }
+
+    @Test
+    public void testDeleteNonExistentChannel() throws Exception {
+        mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
+    }
+
+    @Test
+    public void testGetDeletedChannel() throws Exception {
+        NotificationChannel channel = getChannel();
+        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel.enableLights(true);
+        channel.setBypassDnd(true);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.enableVibration(true);
+        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
+
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+        // Does not return deleted channel
+        NotificationChannel response =
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+        assertNull(response);
+
+        // Returns deleted channel
+        response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
+        compareChannels(channel, response);
+        assertTrue(response.isDeleted());
+    }
+
+    @Test
+    public void testGetDeletedChannels() throws Exception {
+        Map<String, NotificationChannel> channelMap = new HashMap<>();
+        NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel.enableLights(true);
+        channel.setBypassDnd(true);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+        channel.enableVibration(true);
+        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
+        channelMap.put(channel.getId(), channel);
+        NotificationChannel channel2 =
+                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+        channelMap.put(channel2.getId(), channel2);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+        // Returns only non-deleted channels
+        List<NotificationChannel> channels =
+                mHelper.getNotificationChannels(PKG, UID, false).getList();
+        assertEquals(2, channels.size());   // Default channel + non-deleted channel
+        for (NotificationChannel nc : channels) {
+            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+                compareChannels(channel2, nc);
+            }
+        }
+
+        // Returns deleted channels too
+        channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
+        assertEquals(3, channels.size());               // Includes default channel
+        for (NotificationChannel nc : channels) {
+            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+                compareChannels(channelMap.get(nc.getId()), nc);
+            }
+        }
+    }
+
+    @Test
+    public void testGetDeletedChannelCount() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        NotificationChannel channel2 =
+                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel3 =
+                new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
+
+        assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+        assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
+    }
+
+    @Test
+    public void testGetBlockedChannelCount() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        NotificationChannel channel2 =
+                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
+        NotificationChannel channel3 =
+                new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+        mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
+
+        assertEquals(1, mHelper.getBlockedChannelCount(PKG, UID));
+        assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID2));
+    }
+
+    @Test
+    public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
+        // create notification channel that can't bypass dnd
+        // expected result: areChannelsBypassingDnd = false
+        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+        NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
+
+        // create notification channel that can bypass dnd
+        // expected result: areChannelsBypassingDnd = true
+        NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel2.setBypassDnd(true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
+        assertTrue(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+
+        // delete channels
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+        assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
+
+        mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+    }
+
+    @Test
+    public void testUpdateCanChannelsBypassDnd() throws Exception {
+        // create notification channel that can't bypass dnd
+        // expected result: areChannelsBypassingDnd = false
+        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+        NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
+
+        // update channel so it CAN bypass dnd:
+        // expected result: areChannelsBypassingDnd = true
+        channel.setBypassDnd(true);
+        mHelper.updateNotificationChannel(PKG, UID, channel, true);
+        assertTrue(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+
+        // update channel so it can't bypass dnd:
+        // expected result: areChannelsBypassingDnd = false
+        channel.setBypassDnd(false);
+        mHelper.updateNotificationChannel(PKG, UID, channel, true);
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+    }
+
+    @Test
+    public void testSetupNewZenModeHelper_canBypass() {
+        // start notification policy off with mAreChannelsBypassingDnd = true, but
+        // RankingHelper should change to false
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+    }
+
+    @Test
+    public void testSetupNewZenModeHelper_cannotBypass() {
+        // start notification policy off with mAreChannelsBypassingDnd = false
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
+    }
+
+    @Test
+    public void testCreateDeletedChannel() throws Exception {
+        long[] vibration = new long[]{100, 67, 145, 156};
+        NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel.setVibrationPattern(vibration);
+
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+        NotificationChannel newChannel = new NotificationChannel(
+                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+        newChannel.setVibrationPattern(new long[]{100});
+
+        mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
+
+        // No long deleted, using old settings
+        compareChannels(channel,
+                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
+    }
+
+    @Test
+    public void testOnlyHasDefaultChannel() throws Exception {
+        assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID));
+        assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2));
+
+        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+        assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID));
+    }
+
+    @Test
+    public void testCreateChannel_defaultChannelId() throws Exception {
+        try {
+            mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
+                    NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
+            fail("Allowed to create default channel");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testCreateChannel_alreadyExists() throws Exception {
+        long[] vibration = new long[]{100, 67, 145, 156};
+        NotificationChannel channel =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel.setVibrationPattern(vibration);
+
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+        NotificationChannel newChannel = new NotificationChannel(
+                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+        newChannel.setVibrationPattern(new long[]{100});
+
+        mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
+
+        // Old settings not overridden
+        compareChannels(channel,
+                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
+    }
+
+    @Test
+    public void testCreateChannel_noOverrideSound() throws Exception {
+        Uri sound = new Uri.Builder().scheme("test").build();
+        final NotificationChannel channel = new NotificationChannel("id2", "name2",
+                 NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setSound(sound, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+        assertEquals(sound, mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false).getSound());
+    }
+
+    @Test
+    public void testPermanentlyDeleteChannels() throws Exception {
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+
+        mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
+
+        // Only default channel remains
+        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
+    }
+
+    @Test
+    public void testDeleteGroup() throws Exception {
+        NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
+        NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
+        NotificationChannel nonGroupedNonDeletedChannel =
+                new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
+        NotificationChannel groupedButNotDeleted =
+                new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+        groupedButNotDeleted.setGroup("not");
+        NotificationChannel groupedAndDeleted =
+                new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
+        groupedAndDeleted.setGroup("totally");
+
+        mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+        mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true, false);
+        mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false);
+        mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false);
+
+        mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
+
+        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+        assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
+
+        assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
+        compareChannels(groupedAndDeleted,
+                mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
+
+        compareChannels(groupedButNotDeleted,
+                mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
+        compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
+                PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
+
+        // notDeleted
+        assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
+
+        verify(mHandler, never()).requestSort();
+    }
+
+    @Test
+    public void testOnUserRemoved() throws Exception {
+        int[] user0Uids = {98, 235, 16, 3782};
+        int[] user1Uids = new int[user0Uids.length];
+        for (int i = 0; i < user0Uids.length; i++) {
+            user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];
+
+            final ApplicationInfo legacy = new ApplicationInfo();
+            legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+            when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+
+            // create records with the default channel for all user 0 and user 1 uids
+            mHelper.getImportance(PKG, user0Uids[i]);
+            mHelper.getImportance(PKG, user1Uids[i]);
+        }
+
+        mHelper.onUserRemoved(1);
+
+        // user 0 records remain
+        for (int i = 0; i < user0Uids.length; i++) {
+            assertEquals(1,
+                    mHelper.getNotificationChannels(PKG, user0Uids[i], false).getList().size());
+        }
+        // user 1 records are gone
+        for (int i = 0; i < user1Uids.length; i++) {
+            assertEquals(0,
+                    mHelper.getNotificationChannels(PKG, user1Uids[i], false).getList().size());
+        }
+    }
+
+    @Test
+    public void testOnPackageChanged_packageRemoval() throws Exception {
+        // Deleted
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+        assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
+
+        // Not deleted
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+        assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
+    }
+
+    @Test
+    public void testOnPackageChanged_packageRemoval_importance() throws Exception {
+        mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
+
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+    }
+
+    @Test
+    public void testOnPackageChanged_packageRemoval_groups() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+        assertEquals(0,
+                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
+    }
+
+    @Test
+    public void testOnPackageChange_downgradeTargetSdk() throws Exception {
+        // create channel as api 26
+        mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
+
+        // install new app version targeting 25
+        final ApplicationInfo legacy = new ApplicationInfo();
+        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+        when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(legacy);
+        mHelper.onPackagesChanged(
+                false, UserHandle.USER_SYSTEM, new String[]{UPDATED_PKG}, new int[]{UID2});
+
+        // make sure the default channel was readded
+        //assertEquals(2, mHelper.getNotificationChannels(UPDATED_PKG, UID2, false).getList().size());
+        assertNotNull(mHelper.getNotificationChannel(
+                UPDATED_PKG, UID2, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+    }
+
+    @Test
+    public void testRecordDefaults() throws Exception {
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+        assertEquals(true, mHelper.canShowBadge(PKG, UID));
+        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
+    }
+
+    @Test
+    public void testCreateGroup() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
+        verify(mHandler, never()).requestSort();
+    }
+
+    @Test
+    public void testCannotCreateChannel_badGroup() throws Exception {
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        channel1.setGroup("garbage");
+        try {
+            mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+            fail("Created a channel with a bad group");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public void testCannotCreateChannel_goodGroup() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        channel1.setGroup(ncg.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+        assertEquals(ncg.getId(),
+                mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
+    }
+
+    @Test
+    public void testGetChannelGroups() throws Exception {
+        NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
+        mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        channel1.setGroup(ncg.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+        NotificationChannel channel1a =
+                new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
+        channel1a.setGroup(ncg.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel1a, true, false);
+
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
+        channel2.setGroup(ncg2.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+
+        NotificationChannel channel3 =
+                new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+        List<NotificationChannelGroup> actual =
+                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+        assertEquals(3, actual.size());
+        for (NotificationChannelGroup group : actual) {
+            if (group.getId() == null) {
+                assertEquals(2, group.getChannels().size()); // misc channel too
+                assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
+                        || channel3.getId().equals(group.getChannels().get(1).getId()));
+            } else if (group.getId().equals(ncg.getId())) {
+                assertEquals(2, group.getChannels().size());
+                if (group.getChannels().get(0).getId().equals(channel1.getId())) {
+                    assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
+                } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
+                    assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
+                } else {
+                    fail("expected channel not found");
+                }
+            } else if (group.getId().equals(ncg2.getId())) {
+                assertEquals(1, group.getChannels().size());
+                assertEquals(channel2.getId(), group.getChannels().get(0).getId());
+            }
+        }
+    }
+
+    @Test
+    public void testGetChannelGroups_noSideEffects() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        channel1.setGroup(ncg.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+        mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+
+        channel1.setImportance(IMPORTANCE_LOW);
+        mHelper.updateNotificationChannel(PKG, UID, channel1, true);
+
+        List<NotificationChannelGroup> actual =
+                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+
+        assertEquals(2, actual.size());
+        for (NotificationChannelGroup group : actual) {
+            if (Objects.equals(group.getId(), ncg.getId())) {
+                assertEquals(1, group.getChannels().size());
+            }
+        }
+    }
+
+    @Test
+    public void testCreateChannel_updateName() throws Exception {
+        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+        NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+        assertEquals("hello", actual.getName());
+
+        nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+
+        actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+        assertEquals("goodbye", actual.getName());
+        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+        verify(mHandler, times(1)).requestSort();
+    }
+
+    @Test
+    public void testCreateChannel_addToGroup() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+        NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+        assertNull(actual.getGroup());
+
+        nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
+        nc.setGroup(group.getId());
+        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+
+        actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+        assertNotNull(actual.getGroup());
+        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+        verify(mHandler, times(1)).requestSort();
+    }
+
+    @Test
+    public void testDumpChannelsJson() throws Exception {
+        final ApplicationInfo upgrade = new ApplicationInfo();
+        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
+        try {
+            when(mPm.getApplicationInfoAsUser(
+                    anyString(), anyInt(), anyInt())).thenReturn(upgrade);
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
+        int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
+        for (int i = 0; i < numPackages; i++) {
+            String pkgName = "pkg" + i;
+            int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
+            for (int j = 0; j < numChannels; j++) {
+                mHelper.createNotificationChannel(pkgName, UID,
+                        new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
+            }
+            expectedChannels.put(pkgName, numChannels);
+        }
+
+        // delete the first channel of the first package
+        String pkg = expectedChannels.keyAt(0);
+        mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
+        // dump should not include deleted channels
+        int count = expectedChannels.get(pkg);
+        expectedChannels.put(pkg, count - 1);
+
+        JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
+        assertEquals(numPackages, actual.length());
+        for (int i = 0; i < numPackages; i++) {
+            JSONObject object = actual.getJSONObject(i);
+            assertTrue(expectedChannels.containsKey(object.get("packageName")));
+            assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
+                    object.getInt("channelCount"));
+        }
+    }
+
+    @Test
+    public void testBadgingOverrideTrue() throws Exception {
+        Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NOTIFICATION_BADGING, 1,
+                USER.getIdentifier());
+        mHelper.updateBadgingEnabled(); // would be called by settings observer
+        assertTrue(mHelper.badgingEnabled(USER));
+    }
+
+    @Test
+    public void testBadgingOverrideFalse() throws Exception {
+        Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NOTIFICATION_BADGING, 0,
+                USER.getIdentifier());
+        mHelper.updateBadgingEnabled(); // would be called by settings observer
+        assertFalse(mHelper.badgingEnabled(USER));
+    }
+
+    @Test
+    public void testBadgingForUserAll() throws Exception {
+        try {
+            mHelper.badgingEnabled(UserHandle.ALL);
+        } catch (Exception e) {
+            fail("just don't throw");
+        }
+    }
+
+    @Test
+    public void testBadgingOverrideUserIsolation() throws Exception {
+        Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NOTIFICATION_BADGING, 0,
+                USER.getIdentifier());
+        Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.NOTIFICATION_BADGING, 1,
+                USER2.getIdentifier());
+        mHelper.updateBadgingEnabled(); // would be called by settings observer
+        assertFalse(mHelper.badgingEnabled(USER));
+        assertTrue(mHelper.badgingEnabled(USER2));
+    }
+
+    @Test
+    public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
+        String newLabel = "bananas!";
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        assertFalse(newLabel.equals(defaultChannel.getName()));
+
+        Resources res = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(res);
+        when(res.getString(com.android.internal.R.string.default_notification_channel_label))
+                .thenReturn(newLabel);
+
+        mHelper.onLocaleChanged(mContext, USER.getIdentifier());
+
+        assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
+    }
+
+    @Test
+    public void testIsGroupBlocked_noGroup() throws Exception {
+        assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
+
+        assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
+    }
+
+    @Test
+    public void testIsGroupBlocked_notBlocked() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+
+        assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+    }
+
+    @Test
+    public void testIsGroupBlocked_blocked() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+        group.setBlocked(true);
+        mHelper.createNotificationChannelGroup(PKG, UID, group, false);
+
+        assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+    }
+
+    @Test
+    public void testIsGroup_appCannotResetBlock() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+        NotificationChannelGroup group2 = group.clone();
+        group2.setBlocked(true);
+        mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
+        assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+
+        NotificationChannelGroup group3 = group.clone();
+        group3.setBlocked(false);
+        mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
+        assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+    }
+
+    @Test
+    public void testGetNotificationChannelGroupWithChannels() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+        NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, other, true);
+
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        a.setGroup(group.getId());
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
+        b.setGroup(other.getId());
+        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+        c.setGroup(group.getId());
+        NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
+
+        mHelper.createNotificationChannel(PKG, UID, a, true, false);
+        mHelper.createNotificationChannel(PKG, UID, b, true, false);
+        mHelper.createNotificationChannel(PKG, UID, c, true, false);
+        mHelper.createNotificationChannel(PKG, UID, d, true, false);
+        mHelper.deleteNotificationChannel(PKG, UID, c.getId());
+
+        NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
+                PKG, UID, group.getId(), true);
+        assertEquals(2, retrieved.getChannels().size());
+        compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+        compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
+
+        retrieved = mHelper.getNotificationChannelGroupWithChannels(
+                PKG, UID, group.getId(), false);
+        assertEquals(1, retrieved.getChannels().size());
+        compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+    }
+
+    @Test
+    public void testAndroidPkgCannotBypassDnd_creation() {
+        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        test.setBypassDnd(true);
+
+        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
+
+        assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
+                .canBypassDnd());
+    }
+
+    @Test
+    public void testDndPkgCanBypassDnd_creation() {
+        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        test.setBypassDnd(true);
+
+        mHelper.createNotificationChannel(PKG, UID, test, true, true);
+
+        assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
+    }
+
+    @Test
+    public void testNormalPkgCannotBypassDnd_creation() {
+        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        test.setBypassDnd(true);
+
+        mHelper.createNotificationChannel(PKG, 1000, test, true, false);
+
+        assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
+    }
+
+    @Test
+    public void testAndroidPkgCannotBypassDnd_update() throws Exception {
+        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
+
+        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        update.setBypassDnd(true);
+        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
+
+        assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
+                .canBypassDnd());
+    }
+
+    @Test
+    public void testDndPkgCanBypassDnd_update() throws Exception {
+        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG, UID, test, true, true);
+
+        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        update.setBypassDnd(true);
+        mHelper.createNotificationChannel(PKG, UID, update, true, true);
+
+        assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
+    }
+
+    @Test
+    public void testNormalPkgCannotBypassDnd_update() {
+        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG, 1000, test, true, false);
+        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+        update.setBypassDnd(true);
+        mHelper.createNotificationChannel(PKG, 1000, update, true, false);
+        assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
+    }
+
+    @Test
+    public void testGetBlockedAppCount_noApps() {
+        assertEquals(0, mHelper.getBlockedAppCount(0));
+    }
+
+    @Test
+    public void testGetBlockedAppCount_noAppsForUserId() {
+        mHelper.setEnabled(PKG, 100, false);
+        assertEquals(0, mHelper.getBlockedAppCount(9));
+    }
+
+    @Test
+    public void testGetBlockedAppCount_appsForUserId() {
+        mHelper.setEnabled(PKG, 1020, false);
+        mHelper.setEnabled(PKG, 1030, false);
+        mHelper.setEnabled(PKG, 1060, false);
+        mHelper.setEnabled(PKG, 1000, true);
+        assertEquals(3, mHelper.getBlockedAppCount(0));
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 98c6ec4..7e0fcc9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -15,35 +15,17 @@
  */
 package com.android.server.notification;
 
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MAX;
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.ContentProvider;
 import android.content.Context;
@@ -52,46 +34,26 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
-import android.content.res.Resources;
-import android.graphics.Color;
 import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Build;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
-import android.util.ArrayMap;
-import android.util.Xml;
 
-import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
 
-import org.json.JSONArray;
-import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ThreadLocalRandom;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -118,6 +80,7 @@
     @Mock IContentProvider mTestIContentProvider;
     @Mock Context mContext;
     @Mock ZenModeHelper mMockZenModeHelper;
+    @Mock RankingConfig mConfig;
 
     private NotificationManager.Policy mTestNotificationPolicy;
     private Notification mNotiGroupGSortA;
@@ -179,9 +142,8 @@
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
-        mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
                 mUsageStats, new String[] {ImportanceExtractor.class.getName()});
-        resetZenModeHelper();
 
         mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("A")
@@ -240,74 +202,6 @@
                 IMPORTANCE_LOW);
     }
 
-    private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
-            String... channelIds)
-            throws Exception {
-        XmlSerializer serializer = new FastXmlSerializer();
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
-        serializer.startDocument(null, true);
-        mHelper.writeXml(serializer, forBackup);
-        serializer.endDocument();
-        serializer.flush();
-        for (String channelId : channelIds) {
-            mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
-        }
-        return baos;
-    }
-
-    private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
-        loadByteArrayXml(stream.toByteArray(), forRestore);
-    }
-
-    private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
-        parser.nextTag();
-        mHelper.readXml(parser, forRestore);
-    }
-
-    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
-        assertEquals(expected.getId(), actual.getId());
-        assertEquals(expected.getName(), actual.getName());
-        assertEquals(expected.getDescription(), actual.getDescription());
-        assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
-        assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
-        assertEquals(expected.getImportance(), actual.getImportance());
-        assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
-        assertEquals(expected.getSound(), actual.getSound());
-        assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
-        assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
-        assertEquals(expected.getGroup(), actual.getGroup());
-        assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
-        assertEquals(expected.getLightColor(), actual.getLightColor());
-    }
-
-    private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
-        assertEquals(expected.getId(), actual.getId());
-        assertEquals(expected.getName(), actual.getName());
-        assertEquals(expected.getDescription(), actual.getDescription());
-        assertEquals(expected.isBlocked(), actual.isBlocked());
-    }
-
-    private NotificationChannel getChannel() {
-        return new NotificationChannel("id", "name", IMPORTANCE_LOW);
-    }
-
-    private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
-        for (NotificationChannel channel : channels) {
-            if (channel.getId().equals(id)) {
-                return channel;
-            }
-        }
-        return null;
-    }
-
-    private void resetZenModeHelper() {
-        reset(mMockZenModeHelper);
-        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
-    }
-
     @Test
     public void testFindAfterRankingWithASplitGroup() throws Exception {
         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -357,1496 +251,4 @@
         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
         mHelper.sort(notificationList);
     }
-
-    @Test
-    public void testChannelXml() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
-        ncg.setBlocked(true);
-        ncg.setDescription("group desc");
-        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel channel2 =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel2.setDescription("descriptions for all");
-        channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
-        channel2.enableLights(true);
-        channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        channel2.enableVibration(true);
-        channel2.setGroup(ncg.getId());
-        channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
-        channel2.setLightColor(Color.BLUE);
-
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-
-        mHelper.setShowBadge(PKG, UID, true);
-        mHelper.setAppImportanceLocked(PKG, UID);
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
-                channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
-
-        loadStreamXml(baos, false);
-
-        assertTrue(mHelper.canShowBadge(PKG, UID));
-        assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
-        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
-        compareChannels(channel2,
-                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-
-        List<NotificationChannelGroup> actualGroups =
-                mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
-        boolean foundNcg = false;
-        for (NotificationChannelGroup actual : actualGroups) {
-            if (ncg.getId().equals(actual.getId())) {
-                foundNcg = true;
-                compareGroups(ncg, actual);
-            } else if (ncg2.getId().equals(actual.getId())) {
-                compareGroups(ncg2, actual);
-            }
-        }
-        assertTrue(foundNcg);
-
-        boolean foundChannel2Group = false;
-        for (NotificationChannelGroup actual : actualGroups) {
-            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
-                foundChannel2Group = true;
-                break;
-            }
-        }
-        assertTrue(foundChannel2Group);
-    }
-
-    @Test
-    public void testChannelXmlForBackup() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
-        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel channel2 =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel2.setDescription("descriptions for all");
-        channel2.setSound(SOUND_URI, mAudioAttributes);
-        channel2.enableLights(true);
-        channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        channel2.enableVibration(false);
-        channel2.setGroup(ncg.getId());
-        channel2.setLightColor(Color.BLUE);
-        NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
-        channel3.enableVibration(true);
-
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-        mHelper.createNotificationChannel(PKG, UID, channel3, false, false);
-        mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
-
-        mHelper.setShowBadge(PKG, UID, true);
-
-        mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
-                channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG},
-                new int[]{UID, UID2});
-
-        mHelper.setShowBadge(UPDATED_PKG, UID2, true);
-
-        loadStreamXml(baos, true);
-
-        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
-        assertTrue(mHelper.canShowBadge(PKG, UID));
-        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
-        compareChannels(channel2,
-                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-        compareChannels(channel3,
-                mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
-
-        List<NotificationChannelGroup> actualGroups =
-                mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
-        boolean foundNcg = false;
-        for (NotificationChannelGroup actual : actualGroups) {
-            if (ncg.getId().equals(actual.getId())) {
-                foundNcg = true;
-                compareGroups(ncg, actual);
-            } else if (ncg2.getId().equals(actual.getId())) {
-                compareGroups(ncg2, actual);
-            }
-        }
-        assertTrue(foundNcg);
-
-        boolean foundChannel2Group = false;
-        for (NotificationChannelGroup actual : actualGroups) {
-            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
-                foundChannel2Group = true;
-                break;
-            }
-        }
-        assertTrue(foundChannel2Group);
-    }
-
-    @Test
-    public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
-        NotificationChannel channel =
-                new NotificationChannel("id", "name", IMPORTANCE_LOW);
-        channel.setSound(SOUND_URI, mAudioAttributes);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
-        // Testing that in restore we are given the canonical version
-        loadStreamXml(baos, true);
-        verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
-    }
-
-    @Test
-    public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
-        Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
-        Uri canonicalBasedOnLocal = localUri.buildUpon()
-                .appendQueryParameter("title", "Test")
-                .appendQueryParameter("canonical", "1")
-                .build();
-        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
-                .thenReturn(canonicalBasedOnLocal);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
-                .thenReturn(localUri);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
-                .thenReturn(localUri);
-
-        NotificationChannel channel =
-                new NotificationChannel("id", "name", IMPORTANCE_LOW);
-        channel.setSound(SOUND_URI, mAudioAttributes);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
-        loadStreamXml(baos, true);
-
-        NotificationChannel actualChannel = mHelper.getNotificationChannel(
-                PKG, UID, channel.getId(), false);
-        assertEquals(localUri, actualChannel.getSound());
-    }
-
-    @Test
-    public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
-        Thread.sleep(3000);
-        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
-                .thenReturn(null);
-        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
-                .thenReturn(null);
-
-        NotificationChannel channel =
-                new NotificationChannel("id", "name", IMPORTANCE_LOW);
-        channel.setSound(SOUND_URI, mAudioAttributes);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
-        loadStreamXml(baos, true);
-
-        NotificationChannel actualChannel = mHelper.getNotificationChannel(
-                PKG, UID, channel.getId(), false);
-        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
-    }
-
-
-    /**
-     * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
-     * handle its restore properly.
-     */
-    @Test
-    public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
-        // Not a local uncanonicalized uri, simulating that it fails to exist locally
-        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
-        String id = "id";
-        String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
-                + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
-                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
-                + "sound=\"" + SOUND_URI + "\" "
-                + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
-                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "</package>\n"
-                + "</ranking>\n";
-
-        loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
-
-        NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
-        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
-    }
-
-    @Test
-    public void testBackupRestoreXml_withNullSoundUri() throws Exception {
-        NotificationChannel channel =
-                new NotificationChannel("id", "name", IMPORTANCE_LOW);
-        channel.setSound(null, mAudioAttributes);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
-        loadStreamXml(baos, true);
-
-        NotificationChannel actualChannel = mHelper.getNotificationChannel(
-                PKG, UID, channel.getId(), false);
-        assertEquals(null, actualChannel.getSound());
-    }
-
-    @Test
-    public void testChannelXml_backup() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
-        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel channel2 =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        NotificationChannel channel3 =
-                new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
-        channel3.setGroup(ncg.getId());
-
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
-        mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
-        mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
-        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
-                channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
-
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, true);
-
-        assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
-        assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
-        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
-        //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
-        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-    }
-
-    @Test
-    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
-                NotificationChannel.DEFAULT_CHANNEL_ID);
-
-        loadStreamXml(baos, false);
-
-        final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
-        assertFalse(updated.canBypassDnd());
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
-        assertEquals(0, updated.getUserLockedFields());
-    }
-
-    @Test
-    public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
-        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
-                NotificationChannel.DEFAULT_CHANNEL_ID);
-
-        loadStreamXml(baos, false);
-
-        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
-                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
-    }
-
-    @Test
-    public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
-        final String preupgradeXml = "<ranking version=\"1\">\n"
-                + "<package name=\"" + PKG
-                + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
-                + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
-                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
-                + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
-                + Notification.VISIBILITY_PRIVATE + "\" />\n"
-                + "</ranking>";
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
-
-        final NotificationChannel updated1 =
-            mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
-        assertTrue(updated1.canBypassDnd());
-        assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
-        assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
-                | NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_VISIBILITY,
-                updated1.getUserLockedFields());
-
-        // No Default Channel created for updated packages
-        assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false));
-    }
-
-    @Test
-    public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
-        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
-                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        assertTrue(defaultChannel != null);
-        ByteArrayOutputStream baos =
-                writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
-        // Load package at higher sdk.
-        final ApplicationInfo upgraded = new ApplicationInfo();
-        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
-        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
-        loadStreamXml(baos, false);
-
-        // Default Channel should be gone.
-        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false));
-    }
-
-    @Test
-    public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
-        mHelper.createNotificationChannel(PKG, UID,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
-                NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
-
-        // Load package at higher sdk.
-        final ApplicationInfo upgraded = new ApplicationInfo();
-        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
-        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
-        loadStreamXml(baos, false);
-
-        // Default Channel should be gone.
-        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false));
-    }
-
-    @Test
-    public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
-                NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
-        mHelper.createNotificationChannel(PKG, UID,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
-
-        loadStreamXml(baos, false);
-
-        // Should still have the newly created channel that wasn't in the xml.
-        assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
-    }
-
-    @Test
-    public void testCreateChannel_blocked() throws Exception {
-        mHelper.setImportance(PKG, UID, IMPORTANCE_NONE);
-
-        mHelper.createNotificationChannel(PKG, UID,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
-    }
-
-    @Test
-    public void testCreateChannel_badImportance() throws Exception {
-        try {
-            mHelper.createNotificationChannel(PKG, UID,
-                    new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
-                    true, false);
-            fail("Was allowed to create a channel with invalid importance");
-        } catch (IllegalArgumentException e) {
-            // yay
-        }
-        try {
-            mHelper.createNotificationChannel(PKG, UID,
-                    new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
-                    true, false);
-            fail("Was allowed to create a channel with invalid importance");
-        } catch (IllegalArgumentException e) {
-            // yay
-        }
-        try {
-            mHelper.createNotificationChannel(PKG, UID,
-                    new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
-                    true, false);
-            fail("Was allowed to create a channel with invalid importance");
-        } catch (IllegalArgumentException e) {
-            // yay
-        }
-        mHelper.createNotificationChannel(PKG, UID,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
-        mHelper.createNotificationChannel(PKG, UID,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
-    }
-
-
-    @Test
-    public void testUpdate() throws Exception {
-        // no fields locked by user
-        final NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
-        channel.enableLights(true);
-        channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
-        mHelper.createNotificationChannel(PKG, UID, channel, false, false);
-
-        // same id, try to update all fields
-        final NotificationChannel channel2 =
-                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
-        channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
-        channel2.enableLights(false);
-        channel2.setBypassDnd(false);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
-
-        mHelper.updateNotificationChannel(PKG, UID, channel2, true);
-
-        // all fields should be changed
-        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
-
-        verify(mHandler, times(1)).requestSort();
-    }
-
-    @Test
-    public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
-        mHelper.setImportance(PKG, UID, IMPORTANCE_UNSPECIFIED);
-        assertTrue(mHelper.canShowBadge(PKG, UID));
-        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
-                mHelper.getPackageVisibility(PKG, UID));
-        assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
-
-        NotificationChannel defaultChannel = mHelper.getNotificationChannel(
-                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-
-        defaultChannel.setShowBadge(false);
-        defaultChannel.setImportance(IMPORTANCE_NONE);
-        defaultChannel.setBypassDnd(true);
-        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
-        mHelper.setAppImportanceLocked(PKG, UID);
-        mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
-
-        // ensure app level fields are changed
-        assertFalse(mHelper.canShowBadge(PKG, UID));
-        assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
-        assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
-        assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
-        assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
-    }
-
-    @Test
-    public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
-        final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-
-        mHelper.createNotificationChannel(PKG, UID, channel, false, false);
-        assertTrue(mHelper.canShowBadge(PKG, UID));
-        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
-                mHelper.getPackageVisibility(PKG, UID));
-
-        channel.setShowBadge(false);
-        channel.setImportance(IMPORTANCE_NONE);
-        channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
-        mHelper.updateNotificationChannel(PKG, UID, channel, true);
-
-        // ensure app level fields are not changed
-        assertTrue(mHelper.canShowBadge(PKG, UID));
-        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
-                mHelper.getPackageVisibility(PKG, UID));
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
-    }
-
-    @Test
-    public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
-        assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
-    }
-
-    @Test
-    public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
-        final NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
-        channel.enableLights(true);
-        channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        channel.setShowBadge(true);
-        int lockMask = 0;
-        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
-            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
-        }
-        channel.lockFields(lockMask);
-
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
-        NotificationChannel savedChannel =
-                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-
-        assertEquals(channel.getName(), savedChannel.getName());
-        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
-        assertFalse(savedChannel.canBypassDnd());
-        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
-        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
-
-        verify(mHandler, never()).requestSort();
-    }
-
-    @Test
-    public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
-        final NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
-        channel.enableLights(true);
-        channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        channel.setShowBadge(true);
-        int lockMask = 0;
-        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
-            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
-        }
-        channel.lockFields(lockMask);
-
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
-        NotificationChannel savedChannel =
-                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-
-        assertEquals(channel.getName(), savedChannel.getName());
-        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
-        assertFalse(savedChannel.canBypassDnd());
-        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
-        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
-    }
-
-    @Test
-    public void testClearLockedFields() throws Exception {
-        final NotificationChannel channel = getChannel();
-        mHelper.clearLockedFields(channel);
-        assertEquals(0, channel.getUserLockedFields());
-
-        channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_IMPORTANCE);
-        mHelper.clearLockedFields(channel);
-        assertEquals(0, channel.getUserLockedFields());
-    }
-
-    @Test
-    public void testLockFields_soundAndVibration() throws Exception {
-        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
-        final NotificationChannel update1 = getChannel();
-        update1.setSound(new Uri.Builder().scheme("test").build(),
-                new AudioAttributes.Builder().build());
-        update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
-        mHelper.updateNotificationChannel(PKG, UID, update1, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_SOUND,
-                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
-                        .getUserLockedFields());
-
-        NotificationChannel update2 = getChannel();
-        update2.enableVibration(true);
-        mHelper.updateNotificationChannel(PKG, UID, update2, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_SOUND
-                        | NotificationChannel.USER_LOCKED_VIBRATION,
-                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
-                        .getUserLockedFields());
-    }
-
-    @Test
-    public void testLockFields_vibrationAndLights() throws Exception {
-        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
-        final NotificationChannel update1 = getChannel();
-        update1.setVibrationPattern(new long[]{7945, 46 ,246});
-        mHelper.updateNotificationChannel(PKG, UID, update1, true);
-        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
-                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
-                        .getUserLockedFields());
-
-        final NotificationChannel update2 = getChannel();
-        update2.enableLights(true);
-        mHelper.updateNotificationChannel(PKG, UID, update2, true);
-        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
-                        | NotificationChannel.USER_LOCKED_LIGHTS,
-                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
-                        .getUserLockedFields());
-    }
-
-    @Test
-    public void testLockFields_lightsAndImportance() throws Exception {
-        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
-        final NotificationChannel update1 = getChannel();
-        update1.setLightColor(Color.GREEN);
-        mHelper.updateNotificationChannel(PKG, UID, update1, true);
-        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
-                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
-                        .getUserLockedFields());
-
-        final NotificationChannel update2 = getChannel();
-        update2.setImportance(IMPORTANCE_DEFAULT);
-        mHelper.updateNotificationChannel(PKG, UID, update2, true);
-        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
-                        | NotificationChannel.USER_LOCKED_IMPORTANCE,
-                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
-                        .getUserLockedFields());
-    }
-
-    @Test
-    public void testLockFields_visibilityAndDndAndBadge() throws Exception {
-        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-        assertEquals(0,
-                mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
-                        .getUserLockedFields());
-
-        final NotificationChannel update1 = getChannel();
-        update1.setBypassDnd(true);
-        mHelper.updateNotificationChannel(PKG, UID, update1, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
-                mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
-                        .getUserLockedFields());
-
-        final NotificationChannel update2 = getChannel();
-        update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        mHelper.updateNotificationChannel(PKG, UID, update2, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_VISIBILITY,
-                mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
-                        .getUserLockedFields());
-
-        final NotificationChannel update3 = getChannel();
-        update3.setShowBadge(false);
-        mHelper.updateNotificationChannel(PKG, UID, update3, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_VISIBILITY
-                        | NotificationChannel.USER_LOCKED_SHOW_BADGE,
-                mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
-                        .getUserLockedFields());
-    }
-
-    @Test
-    public void testDeleteNonExistentChannel() throws Exception {
-        mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
-    }
-
-    @Test
-    public void testGetDeletedChannel() throws Exception {
-        NotificationChannel channel = getChannel();
-        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
-        channel.enableLights(true);
-        channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        channel.enableVibration(true);
-        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
-
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
-        // Does not return deleted channel
-        NotificationChannel response =
-                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-        assertNull(response);
-
-        // Returns deleted channel
-        response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
-        compareChannels(channel, response);
-        assertTrue(response.isDeleted());
-    }
-
-    @Test
-    public void testGetDeletedChannels() throws Exception {
-        Map<String, NotificationChannel> channelMap = new HashMap<>();
-        NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
-        channel.enableLights(true);
-        channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
-        channel.enableVibration(true);
-        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
-        channelMap.put(channel.getId(), channel);
-        NotificationChannel channel2 =
-                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
-        channelMap.put(channel2.getId(), channel2);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-
-        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
-        // Returns only non-deleted channels
-        List<NotificationChannel> channels =
-                mHelper.getNotificationChannels(PKG, UID, false).getList();
-        assertEquals(2, channels.size());   // Default channel + non-deleted channel
-        for (NotificationChannel nc : channels) {
-            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
-                compareChannels(channel2, nc);
-            }
-        }
-
-        // Returns deleted channels too
-        channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
-        assertEquals(3, channels.size());               // Includes default channel
-        for (NotificationChannel nc : channels) {
-            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
-                compareChannels(channelMap.get(nc.getId()), nc);
-            }
-        }
-    }
-
-    @Test
-    public void testGetDeletedChannelCount() throws Exception {
-        NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        NotificationChannel channel2 =
-                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel channel3 =
-                new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
-        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-        mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
-
-        assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
-        assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
-    }
-
-    @Test
-    public void testGetBlockedChannelCount() throws Exception {
-        NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        NotificationChannel channel2 =
-                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
-        NotificationChannel channel3 =
-                new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
-        mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
-
-        assertEquals(1, mHelper.getBlockedChannelCount(PKG, UID));
-        assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID2));
-    }
-
-    @Test
-    public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
-        // create notification channel that can't bypass dnd
-        // expected result: areChannelsBypassingDnd = false
-        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
-        NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
-        resetZenModeHelper();
-
-        // create notification channel that can bypass dnd
-        // expected result: areChannelsBypassingDnd = true
-        NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel2.setBypassDnd(true);
-        mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
-        assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
-        resetZenModeHelper();
-
-        // delete channels
-        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-        assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
-        resetZenModeHelper();
-
-        mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
-        assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
-        resetZenModeHelper();
-    }
-
-    @Test
-    public void testUpdateCanChannelsBypassDnd() throws Exception {
-        // create notification channel that can't bypass dnd
-        // expected result: areChannelsBypassingDnd = false
-        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
-        NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
-        resetZenModeHelper();
-
-        // update channel so it CAN bypass dnd:
-        // expected result: areChannelsBypassingDnd = true
-        channel.setBypassDnd(true);
-        mHelper.updateNotificationChannel(PKG, UID, channel, true);
-        assertTrue(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
-        resetZenModeHelper();
-
-        // update channel so it can't bypass dnd:
-        // expected result: areChannelsBypassingDnd = false
-        channel.setBypassDnd(false);
-        mHelper.updateNotificationChannel(PKG, UID, channel, true);
-        assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
-        resetZenModeHelper();
-    }
-
-    @Test
-    public void testSetupNewZenModeHelper_canBypass() {
-        // start notification policy off with mAreChannelsBypassingDnd = true, but
-        // RankingHelper should change to false
-        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
-        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
-        mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mUsageStats, new String[] {ImportanceExtractor.class.getName()});
-        assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
-        resetZenModeHelper();
-    }
-
-    @Test
-    public void testSetupNewZenModeHelper_cannotBypass() {
-        // start notification policy off with mAreChannelsBypassingDnd = false
-        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
-        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
-        mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mUsageStats, new String[] {ImportanceExtractor.class.getName()});
-        assertFalse(mHelper.areChannelsBypassingDnd());
-        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
-        resetZenModeHelper();
-    }
-
-    @Test
-    public void testCreateDeletedChannel() throws Exception {
-        long[] vibration = new long[]{100, 67, 145, 156};
-        NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel.setVibrationPattern(vibration);
-
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
-        NotificationChannel newChannel = new NotificationChannel(
-                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
-        newChannel.setVibrationPattern(new long[]{100});
-
-        mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
-
-        // No long deleted, using old settings
-        compareChannels(channel,
-                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
-    }
-
-    @Test
-    public void testOnlyHasDefaultChannel() throws Exception {
-        assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID));
-        assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2));
-
-        mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-        assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID));
-    }
-
-    @Test
-    public void testCreateChannel_defaultChannelId() throws Exception {
-        try {
-            mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
-                    NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
-            fail("Allowed to create default channel");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-    }
-
-    @Test
-    public void testCreateChannel_alreadyExists() throws Exception {
-        long[] vibration = new long[]{100, 67, 145, 156};
-        NotificationChannel channel =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        channel.setVibrationPattern(vibration);
-
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
-        NotificationChannel newChannel = new NotificationChannel(
-                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
-        newChannel.setVibrationPattern(new long[]{100});
-
-        mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
-
-        // Old settings not overridden
-        compareChannels(channel,
-                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
-    }
-
-    @Test
-    public void testCreateChannel_noOverrideSound() throws Exception {
-        Uri sound = new Uri.Builder().scheme("test").build();
-        final NotificationChannel channel = new NotificationChannel("id2", "name2",
-                 NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setSound(sound, mAudioAttributes);
-        mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-        assertEquals(sound, mHelper.getNotificationChannel(
-                PKG, UID, channel.getId(), false).getSound());
-    }
-
-    @Test
-    public void testPermanentlyDeleteChannels() throws Exception {
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel channel2 =
-                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-        mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-
-        mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
-
-        // Only default channel remains
-        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
-    }
-
-    @Test
-    public void testDeleteGroup() throws Exception {
-        NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
-        NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
-        NotificationChannel nonGroupedNonDeletedChannel =
-                new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
-        NotificationChannel groupedButNotDeleted =
-                new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
-        groupedButNotDeleted.setGroup("not");
-        NotificationChannel groupedAndDeleted =
-                new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
-        groupedAndDeleted.setGroup("totally");
-
-        mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
-        mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
-        mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true, false);
-        mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false);
-        mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false);
-
-        mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
-
-        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
-        assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
-
-        assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
-        compareChannels(groupedAndDeleted,
-                mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
-
-        compareChannels(groupedButNotDeleted,
-                mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
-        compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
-                PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
-
-        // notDeleted
-        assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
-
-        verify(mHandler, never()).requestSort();
-    }
-
-    @Test
-    public void testOnUserRemoved() throws Exception {
-        int[] user0Uids = {98, 235, 16, 3782};
-        int[] user1Uids = new int[user0Uids.length];
-        for (int i = 0; i < user0Uids.length; i++) {
-            user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];
-
-            final ApplicationInfo legacy = new ApplicationInfo();
-            legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
-            when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
-
-            // create records with the default channel for all user 0 and user 1 uids
-            mHelper.getImportance(PKG, user0Uids[i]);
-            mHelper.getImportance(PKG, user1Uids[i]);
-        }
-
-        mHelper.onUserRemoved(1);
-
-        // user 0 records remain
-        for (int i = 0; i < user0Uids.length; i++) {
-            assertEquals(1,
-                    mHelper.getNotificationChannels(PKG, user0Uids[i], false).getList().size());
-        }
-        // user 1 records are gone
-        for (int i = 0; i < user1Uids.length; i++) {
-            assertEquals(0,
-                    mHelper.getNotificationChannels(PKG, user1Uids[i], false).getList().size());
-        }
-    }
-
-    @Test
-    public void testOnPackageChanged_packageRemoval() throws Exception {
-        // Deleted
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
-        assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
-
-        // Not deleted
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
-        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-        assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
-    }
-
-    @Test
-    public void testOnPackageChanged_packageRemoval_importance() throws Exception {
-        mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
-
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
-    }
-
-    @Test
-    public void testOnPackageChanged_packageRemoval_groups() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
-        assertEquals(0,
-                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
-    }
-
-    @Test
-    public void testOnPackageChange_downgradeTargetSdk() throws Exception {
-        // create channel as api 26
-        mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
-
-        // install new app version targeting 25
-        final ApplicationInfo legacy = new ApplicationInfo();
-        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
-        when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(legacy);
-        mHelper.onPackagesChanged(
-                false, UserHandle.USER_SYSTEM, new String[]{UPDATED_PKG}, new int[]{UID2});
-
-        // make sure the default channel was readded
-        //assertEquals(2, mHelper.getNotificationChannels(UPDATED_PKG, UID2, false).getList().size());
-        assertNotNull(mHelper.getNotificationChannel(
-                UPDATED_PKG, UID2, NotificationChannel.DEFAULT_CHANNEL_ID, false));
-    }
-
-    @Test
-    public void testRecordDefaults() throws Exception {
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
-        assertEquals(true, mHelper.canShowBadge(PKG, UID));
-        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
-    }
-
-    @Test
-    public void testCreateGroup() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
-        verify(mHandler, never()).requestSort();
-    }
-
-    @Test
-    public void testCannotCreateChannel_badGroup() throws Exception {
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        channel1.setGroup("garbage");
-        try {
-            mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-            fail("Created a channel with a bad group");
-        } catch (IllegalArgumentException e) {
-        }
-    }
-
-    @Test
-    public void testCannotCreateChannel_goodGroup() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
-        assertEquals(ncg.getId(),
-                mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
-    }
-
-    @Test
-    public void testGetChannelGroups() throws Exception {
-        NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
-        mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-        NotificationChannel channel1a =
-                new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
-        channel1a.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(PKG, UID, channel1a, true, false);
-
-        NotificationChannel channel2 =
-                new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
-        channel2.setGroup(ncg2.getId());
-        mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-
-        NotificationChannel channel3 =
-                new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
-        List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-        assertEquals(3, actual.size());
-        for (NotificationChannelGroup group : actual) {
-            if (group.getId() == null) {
-                assertEquals(2, group.getChannels().size()); // misc channel too
-                assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
-                        || channel3.getId().equals(group.getChannels().get(1).getId()));
-            } else if (group.getId().equals(ncg.getId())) {
-                assertEquals(2, group.getChannels().size());
-                if (group.getChannels().get(0).getId().equals(channel1.getId())) {
-                    assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
-                } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
-                    assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
-                } else {
-                    fail("expected channel not found");
-                }
-            } else if (group.getId().equals(ncg2.getId())) {
-                assertEquals(1, group.getChannels().size());
-                assertEquals(channel2.getId(), group.getChannels().get(0).getId());
-            }
-        }
-    }
-
-    @Test
-    public void testGetChannelGroups_noSideEffects() throws Exception {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-
-        NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-        mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-
-        channel1.setImportance(IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(PKG, UID, channel1, true);
-
-        List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-
-        assertEquals(2, actual.size());
-        for (NotificationChannelGroup group : actual) {
-            if (Objects.equals(group.getId(), ncg.getId())) {
-                assertEquals(1, group.getChannels().size());
-            }
-        }
-    }
-
-    @Test
-    public void testCreateChannel_updateName() throws Exception {
-        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
-        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-        NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
-        assertEquals("hello", actual.getName());
-
-        nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-
-        actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
-        assertEquals("goodbye", actual.getName());
-        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
-
-        verify(mHandler, times(1)).requestSort();
-    }
-
-    @Test
-    public void testCreateChannel_addToGroup() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
-        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
-        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-        NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
-        assertNull(actual.getGroup());
-
-        nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
-        nc.setGroup(group.getId());
-        mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-
-        actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
-        assertNotNull(actual.getGroup());
-        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
-
-        verify(mHandler, times(1)).requestSort();
-    }
-
-    @Test
-    public void testDumpChannelsJson() throws Exception {
-        final ApplicationInfo upgrade = new ApplicationInfo();
-        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
-        try {
-            when(mPm.getApplicationInfoAsUser(
-                    anyString(), anyInt(), anyInt())).thenReturn(upgrade);
-        } catch (PackageManager.NameNotFoundException e) {
-        }
-        ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
-        int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
-        for (int i = 0; i < numPackages; i++) {
-            String pkgName = "pkg" + i;
-            int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
-            for (int j = 0; j < numChannels; j++) {
-                mHelper.createNotificationChannel(pkgName, UID,
-                        new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
-            }
-            expectedChannels.put(pkgName, numChannels);
-        }
-
-        // delete the first channel of the first package
-        String pkg = expectedChannels.keyAt(0);
-        mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
-        // dump should not include deleted channels
-        int count = expectedChannels.get(pkg);
-        expectedChannels.put(pkg, count - 1);
-
-        JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
-        assertEquals(numPackages, actual.length());
-        for (int i = 0; i < numPackages; i++) {
-            JSONObject object = actual.getJSONObject(i);
-            assertTrue(expectedChannels.containsKey(object.get("packageName")));
-            assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
-                    object.getInt("channelCount"));
-        }
-    }
-
-    @Test
-    public void testBadgingOverrideTrue() throws Exception {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BADGING, 1,
-                USER.getIdentifier());
-        mHelper.updateBadgingEnabled(); // would be called by settings observer
-        assertTrue(mHelper.badgingEnabled(USER));
-    }
-
-    @Test
-    public void testBadgingOverrideFalse() throws Exception {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BADGING, 0,
-                USER.getIdentifier());
-        mHelper.updateBadgingEnabled(); // would be called by settings observer
-        assertFalse(mHelper.badgingEnabled(USER));
-    }
-
-    @Test
-    public void testBadgingForUserAll() throws Exception {
-        try {
-            mHelper.badgingEnabled(UserHandle.ALL);
-        } catch (Exception e) {
-            fail("just don't throw");
-        }
-    }
-
-    @Test
-    public void testBadgingOverrideUserIsolation() throws Exception {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BADGING, 0,
-                USER.getIdentifier());
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BADGING, 1,
-                USER2.getIdentifier());
-        mHelper.updateBadgingEnabled(); // would be called by settings observer
-        assertFalse(mHelper.badgingEnabled(USER));
-        assertTrue(mHelper.badgingEnabled(USER2));
-    }
-
-    @Test
-    public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
-        String newLabel = "bananas!";
-        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        assertFalse(newLabel.equals(defaultChannel.getName()));
-
-        Resources res = mock(Resources.class);
-        when(mContext.getResources()).thenReturn(res);
-        when(res.getString(com.android.internal.R.string.default_notification_channel_label))
-                .thenReturn(newLabel);
-
-        mHelper.onLocaleChanged(mContext, USER.getIdentifier());
-
-        assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
-                NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
-    }
-
-    @Test
-    public void testIsGroupBlocked_noGroup() throws Exception {
-        assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
-
-        assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
-    }
-
-    @Test
-    public void testIsGroupBlocked_notBlocked() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
-        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-
-        assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
-    }
-
-    @Test
-    public void testIsGroupBlocked_blocked() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
-        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-        group.setBlocked(true);
-        mHelper.createNotificationChannelGroup(PKG, UID, group, false);
-
-        assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
-    }
-
-    @Test
-    public void testIsGroup_appCannotResetBlock() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
-        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-        NotificationChannelGroup group2 = group.clone();
-        group2.setBlocked(true);
-        mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
-        assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
-
-        NotificationChannelGroup group3 = group.clone();
-        group3.setBlocked(false);
-        mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
-        assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
-    }
-
-    @Test
-    public void testGetNotificationChannelGroupWithChannels() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
-        NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
-        mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-        mHelper.createNotificationChannelGroup(PKG, UID, other, true);
-
-        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
-        a.setGroup(group.getId());
-        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
-        b.setGroup(other.getId());
-        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
-        c.setGroup(group.getId());
-        NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
-
-        mHelper.createNotificationChannel(PKG, UID, a, true, false);
-        mHelper.createNotificationChannel(PKG, UID, b, true, false);
-        mHelper.createNotificationChannel(PKG, UID, c, true, false);
-        mHelper.createNotificationChannel(PKG, UID, d, true, false);
-        mHelper.deleteNotificationChannel(PKG, UID, c.getId());
-
-        NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
-                PKG, UID, group.getId(), true);
-        assertEquals(2, retrieved.getChannels().size());
-        compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
-        compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
-
-        retrieved = mHelper.getNotificationChannelGroupWithChannels(
-                PKG, UID, group.getId(), false);
-        assertEquals(1, retrieved.getChannels().size());
-        compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
-    }
-
-    @Test
-    public void testAndroidPkgCannotBypassDnd_creation() {
-        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        test.setBypassDnd(true);
-
-        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
-
-        assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
-                .canBypassDnd());
-    }
-
-    @Test
-    public void testDndPkgCanBypassDnd_creation() {
-        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        test.setBypassDnd(true);
-
-        mHelper.createNotificationChannel(PKG, UID, test, true, true);
-
-        assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
-    }
-
-    @Test
-    public void testNormalPkgCannotBypassDnd_creation() {
-        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        test.setBypassDnd(true);
-
-        mHelper.createNotificationChannel(PKG, 1000, test, true, false);
-
-        assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
-    }
-
-    @Test
-    public void testAndroidPkgCannotBypassDnd_update() throws Exception {
-        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
-
-        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        update.setBypassDnd(true);
-        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
-
-        assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
-                .canBypassDnd());
-    }
-
-    @Test
-    public void testDndPkgCanBypassDnd_update() throws Exception {
-        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(PKG, UID, test, true, true);
-
-        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        update.setBypassDnd(true);
-        mHelper.createNotificationChannel(PKG, UID, update, true, true);
-
-        assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
-    }
-
-    @Test
-    public void testNormalPkgCannotBypassDnd_update() {
-        NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(PKG, 1000, test, true, false);
-        NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
-        update.setBypassDnd(true);
-        mHelper.createNotificationChannel(PKG, 1000, update, true, false);
-        assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
-    }
-
-    @Test
-    public void testGetBlockedAppCount_noApps() {
-        assertEquals(0, mHelper.getBlockedAppCount(0));
-    }
-
-    @Test
-    public void testGetBlockedAppCount_noAppsForUserId() {
-        mHelper.setEnabled(PKG, 100, false);
-        assertEquals(0, mHelper.getBlockedAppCount(9));
-    }
-
-    @Test
-    public void testGetBlockedAppCount_appsForUserId() {
-        mHelper.setEnabled(PKG, 1020, false);
-        mHelper.setEnabled(PKG, 1030, false);
-        mHelper.setEnabled(PKG, 1060, false);
-        mHelper.setEnabled(PKG, 1000, true);
-        assertEquals(3, mHelper.getBlockedAppCount(0));
-    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 5239fe5..35f64a1 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -974,7 +974,8 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
-                        PackageManager.MATCH_ANY_USER, userId);
+                        PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
                 // Caller cannot set their own standby state
                 if (packageUid == callingUid) {
                     throw new IllegalArgumentException("Cannot set your own standby bucket");
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 890a6ea..2a41829 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -175,7 +175,10 @@
         }
 
         CellIdentity o = (CellIdentity) other;
-        return TextUtils.equals(mAlphaLong, o.mAlphaLong)
+        return mType == o.mType
+                && TextUtils.equals(mMccStr, o.mMccStr)
+                && TextUtils.equals(mMncStr, o.mMncStr)
+                && TextUtils.equals(mAlphaLong, o.mAlphaLong)
                 && TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
@@ -233,4 +236,4 @@
     protected void log(String s) {
         Rlog.w(mTag, s);
     }
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 18ab6d4..b99fe46 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -16,9 +16,7 @@
 
 package android.telephony;
 
-import android.annotation.Nullable;
 import android.os.Parcel;
-import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -35,6 +33,8 @@
     private final int mCid;
     // 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown.
     private final int mCpid;
+    // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
+    private final int mUarfcn;
 
     /**
      * @hide
@@ -44,6 +44,7 @@
         mLac = Integer.MAX_VALUE;
         mCid = Integer.MAX_VALUE;
         mCpid = Integer.MAX_VALUE;
+        mUarfcn = Integer.MAX_VALUE;
     }
 
     /**
@@ -52,11 +53,12 @@
      * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
      * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
      * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+     * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
      *
      * @hide
      */
-    public CellIdentityTdscdma(int mcc, int mnc, int lac, int cid, int cpid) {
-        this(String.valueOf(mcc), String.valueOf(mnc), lac, cid, cpid, null, null);
+    public CellIdentityTdscdma(int mcc, int mnc, int lac, int cid, int cpid, int uarfcn) {
+        this(String.valueOf(mcc), String.valueOf(mnc), lac, cid, cpid, uarfcn, null, null);
     }
 
     /**
@@ -65,39 +67,24 @@
      * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
      * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
      * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
-     *
-     * FIXME: This is a temporary constructor to facilitate migration.
-     * @hide
-     */
-    public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid) {
-        super(TAG, TYPE_TDSCDMA, mcc, mnc, null, null);
-        mLac = lac;
-        mCid = cid;
-        mCpid = cpid;
-    }
-
-    /**
-     * @param mcc 3-digit Mobile Country Code in string format
-     * @param mnc 2 or 3-digit Mobile Network Code in string format
-     * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
-     * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
-     * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+     * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
      *
      * @hide
      */
-    public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid,
+    public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid, int uarfcn,
             String alphal, String alphas) {
         super(TAG, TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
         mLac = lac;
         mCid = cid;
         mCpid = cpid;
+        mUarfcn = uarfcn;
     }
 
     private CellIdentityTdscdma(CellIdentityTdscdma cid) {
         this(cid.mMccStr, cid.mMncStr, cid.mLac, cid.mCid,
-                cid.mCpid, cid.mAlphaLong, cid.mAlphaShort);
+                cid.mCpid, cid.mUarfcn, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityTdscdma copy() {
@@ -141,9 +128,10 @@
         return mCpid;
     }
 
+    /** @hide */
     @Override
-    public int hashCode() {
-        return Objects.hash(mLac, mCid, mCpid, super.hashCode());
+    public int getChannelNumber() {
+        return mUarfcn;
     }
 
     @Override
@@ -157,24 +145,29 @@
         }
 
         CellIdentityTdscdma o = (CellIdentityTdscdma) other;
-        return TextUtils.equals(mMccStr, o.mMccStr)
-                && TextUtils.equals(mMncStr, o.mMncStr)
-                && mLac == o.mLac
+        return  mLac == o.mLac
                 && mCid == o.mCid
                 && mCpid == o.mCpid
+                && mUarfcn == o.mUarfcn
                 && super.equals(other);
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(mLac, mCid, mCpid, mUarfcn, super.hashCode());
+    }
+
+    @Override
     public String toString() {
         return new StringBuilder(TAG)
         .append(":{ mMcc=").append(mMccStr)
         .append(" mMnc=").append(mMncStr)
+        .append(" mAlphaLong=").append(mAlphaLong)
+        .append(" mAlphaShort=").append(mAlphaShort)
         .append(" mLac=").append(mLac)
         .append(" mCid=").append(mCid)
         .append(" mCpid=").append(mCpid)
-        .append(" mAlphaLong=").append(mAlphaLong)
-        .append(" mAlphaShort=").append(mAlphaShort)
+        .append(" mUarfcn=").append(mUarfcn)
         .append("}").toString();
     }
 
@@ -186,6 +179,7 @@
         dest.writeInt(mLac);
         dest.writeInt(mCid);
         dest.writeInt(mCpid);
+        dest.writeInt(mUarfcn);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -194,7 +188,7 @@
         mLac = in.readInt();
         mCid = in.readInt();
         mCpid = in.readInt();
-
+        mUarfcn = in.readInt();
         if (DBG) log(toString());
     }
 
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 984483e..43f9406 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -35,7 +35,7 @@
     private final int mCid;
     // 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511
     private final int mPsc;
-    // 16-bit UMTS Absolute RF Channel Number
+    // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.4
     private final int mUarfcn;
 
     /**
@@ -70,7 +70,7 @@
      * @param lac 16-bit Location Area Code, 0..65535
      * @param cid 28-bit UMTS Cell Identity
      * @param psc 9-bit UMTS Primary Scrambling Code
-     * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+     * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
      *
      * @hide
      */
@@ -83,7 +83,7 @@
      * @param lac 16-bit Location Area Code, 0..65535
      * @param cid 28-bit UMTS Cell Identity
      * @param psc 9-bit UMTS Primary Scrambling Code
-     * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+     * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
      * @param mccStr 3-digit Mobile Country Code in string format
      * @param mncStr 2 or 3-digit Mobile Network Code in string format
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index 9232ed7..3aab3fc 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -36,6 +37,8 @@
     protected static final int TYPE_LTE = 3;
     /** @hide */
     protected static final int TYPE_WCDMA = 4;
+    /** @hide */
+    protected static final int TYPE_TDCDMA = 5;
 
     // Type to distinguish where time stamp gets recorded.
 
@@ -260,6 +263,7 @@
                     case TYPE_CDMA: return CellInfoCdma.createFromParcelBody(in);
                     case TYPE_LTE: return CellInfoLte.createFromParcelBody(in);
                     case TYPE_WCDMA: return CellInfoWcdma.createFromParcelBody(in);
+                    case TYPE_TDCDMA: return CellInfoTdscdma.createFromParcelBody(in);
                     default: throw new RuntimeException("Bad CellInfo Parcel");
                 }
         }
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 6f2f1f6..6403bc5 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -21,7 +21,7 @@
 import android.telephony.Rlog;
 
 /**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a CDMA cell that provides identity and measurement info.
  */
 public final class CellInfoCdma extends CellInfo implements Parcelable {
 
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 1bedddb..a3a9b31 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -21,7 +21,7 @@
 import android.telephony.Rlog;
 
 /**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a GSM cell that provides identity and measurement info.
  */
 public final class CellInfoGsm extends CellInfo implements Parcelable {
 
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 287c9f0..b892e89 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -21,7 +21,7 @@
 import android.telephony.Rlog;
 
 /**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing an LTE cell that provides identity and measurement info.
  */
 public final class CellInfoLte extends CellInfo implements Parcelable {
 
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
new file mode 100644
index 0000000..7084c51
--- /dev/null
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 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.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link CellInfo} representing a TD-SCDMA cell that provides identity and measurement info.
+ *
+ * @hide
+ */
+public final class CellInfoTdscdma extends CellInfo implements Parcelable {
+
+    private static final String LOG_TAG = "CellInfoTdscdma";
+    private static final boolean DBG = false;
+
+    private CellIdentityTdscdma mCellIdentityTdscdma;
+    private CellSignalStrengthTdscdma mCellSignalStrengthTdscdma;
+
+    /** @hide */
+    public CellInfoTdscdma() {
+        super();
+        mCellIdentityTdscdma = new CellIdentityTdscdma();
+        mCellSignalStrengthTdscdma = new CellSignalStrengthTdscdma();
+    }
+
+    /** @hide */
+    public CellInfoTdscdma(CellInfoTdscdma ci) {
+        super(ci);
+        this.mCellIdentityTdscdma = ci.mCellIdentityTdscdma.copy();
+        this.mCellSignalStrengthTdscdma = ci.mCellSignalStrengthTdscdma.copy();
+    }
+
+    public CellIdentityTdscdma getCellIdentity() {
+        return mCellIdentityTdscdma;
+    }
+    /** @hide */
+    public void setCellIdentity(CellIdentityTdscdma cid) {
+        mCellIdentityTdscdma = cid;
+    }
+
+    public CellSignalStrengthTdscdma getCellSignalStrength() {
+        return mCellSignalStrengthTdscdma;
+    }
+    /** @hide */
+    public void setCellSignalStrength(CellSignalStrengthTdscdma css) {
+        mCellSignalStrengthTdscdma = css;
+    }
+
+    /**
+     * @return hash code
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mCellIdentityTdscdma, mCellSignalStrengthTdscdma);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!super.equals(other)) {
+            return false;
+        }
+        try {
+            CellInfoTdscdma o = (CellInfoTdscdma) other;
+            return mCellIdentityTdscdma.equals(o.mCellIdentityTdscdma)
+                    && mCellSignalStrengthTdscdma.equals(o.mCellSignalStrengthTdscdma);
+        } catch (ClassCastException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("CellInfoTdscdma:{");
+        sb.append(super.toString());
+        sb.append(" ").append(mCellIdentityTdscdma);
+        sb.append(" ").append(mCellSignalStrengthTdscdma);
+        sb.append("}");
+
+        return sb.toString();
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags, TYPE_TDCDMA);
+        mCellIdentityTdscdma.writeToParcel(dest, flags);
+        mCellSignalStrengthTdscdma.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Construct a CellInfoTdscdma object from the given parcel
+     * where the token is already been processed.
+     */
+    private CellInfoTdscdma(Parcel in) {
+        super(in);
+        mCellIdentityTdscdma = CellIdentityTdscdma.CREATOR.createFromParcel(in);
+        mCellSignalStrengthTdscdma = CellSignalStrengthTdscdma.CREATOR.createFromParcel(in);
+    }
+
+    /** Implement the Parcelable interface */
+    public static final Creator<CellInfoTdscdma> CREATOR = new Creator<CellInfoTdscdma>() {
+        @Override
+        public CellInfoTdscdma createFromParcel(Parcel in) {
+            in.readInt(); // Skip past token, we know what it is
+            return createFromParcelBody(in);
+        }
+
+        @Override
+        public CellInfoTdscdma[] newArray(int size) {
+            return new CellInfoTdscdma[size];
+        }
+    };
+
+    /** @hide */
+    protected static CellInfoTdscdma createFromParcelBody(Parcel in) {
+        return new CellInfoTdscdma(in);
+    }
+
+    /**
+     * log
+     */
+    private static void log(String s) {
+        Rlog.w(LOG_TAG, s);
+    }
+}
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index 0615702..005f3d3 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -20,8 +20,10 @@
 import android.os.Parcelable;
 import android.telephony.Rlog;
 
+import java.util.Objects;
+
 /**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a WCDMA cell that provides identity and measurement info.
  */
 public final class CellInfoWcdma extends CellInfo implements Parcelable {
 
@@ -66,7 +68,7 @@
      */
     @Override
     public int hashCode() {
-        return super.hashCode() + mCellIdentityWcdma.hashCode() + mCellSignalStrengthWcdma.hashCode();
+        return Objects.hash(super.hashCode(), mCellIdentityWcdma, mCellSignalStrengthWcdma);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 183f96d..aa6b207 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -104,7 +104,10 @@
     }
 
     /**
-     * Get signal level as an int from 0..4
+     * Retrieve an abstract level value for the overall signal strength.
+     *
+     * @return a single integer from 0 to 4 representing the general signal quality.
+     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
      */
     @Override
     public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 8687cd1..cff159b 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -82,7 +82,10 @@
     }
 
     /**
-     * Get signal level as an int from 0..4
+     * Retrieve an abstract level value for the overall signal strength.
+     *
+     * @return a single integer from 0 to 4 representing the general signal quality.
+     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
      */
     @Override
     public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 2b6928e..2f059f4 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -86,7 +86,10 @@
     }
 
     /**
-     * Get signal level as an int from 0..4
+     * Retrieve an abstract level value for the overall signal strength.
+     *
+     * @return a single integer from 0 to 4 representing the general signal quality.
+     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
      */
     @Override
     public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
new file mode 100644
index 0000000..41859a3
--- /dev/null
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2018 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.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Tdscdma signal strength related information.
+ *
+ * @hide
+ */
+public final class CellSignalStrengthTdscdma extends CellSignalStrength implements Parcelable {
+
+    private static final String LOG_TAG = "CellSignalStrengthTdscdma";
+    private static final boolean DBG = false;
+
+    private static final int TDSCDMA_SIGNAL_STRENGTH_GREAT = 12;
+    private static final int TDSCDMA_SIGNAL_STRENGTH_GOOD = 8;
+    private static final int TDSCDMA_SIGNAL_STRENGTH_MODERATE = 5;
+
+    private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
+                                 // or Integer.MAX_VALUE if unknown
+    private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+                               // Integer.MAX_VALUE if unknown
+    private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or Integer.MAX_VALUE
+                       // if unknown
+
+    /** @hide */
+    public CellSignalStrengthTdscdma() {
+        setDefaultValues();
+    }
+
+    /** @hide */
+    public CellSignalStrengthTdscdma(int ss, int ber, int rscp) {
+        mSignalStrength = ss;
+        mBitErrorRate = ber;
+        mRscp = rscp;
+    }
+
+    /** @hide */
+    public CellSignalStrengthTdscdma(CellSignalStrengthTdscdma s) {
+        copyFrom(s);
+    }
+
+    /** @hide */
+    protected void copyFrom(CellSignalStrengthTdscdma s) {
+        mSignalStrength = s.mSignalStrength;
+        mBitErrorRate = s.mBitErrorRate;
+        mRscp = s.mRscp;
+    }
+
+    /** @hide */
+    @Override
+    public CellSignalStrengthTdscdma copy() {
+        return new CellSignalStrengthTdscdma(this);
+    }
+
+    /** @hide */
+    @Override
+    public void setDefaultValues() {
+        mSignalStrength = Integer.MAX_VALUE;
+        mBitErrorRate = Integer.MAX_VALUE;
+        mRscp = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Retrieve an abstract level value for the overall signal strength.
+     *
+     * @return a single integer from 0 to 4 representing the general signal quality.
+     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
+     */
+    @Override
+    public int getLevel() {
+        int level;
+
+        // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+        // asu = 0 (-113dB or less) is very weak
+        // signal, its better to show 0 bars to the user in such cases.
+        // asu = 99 is a special case, where the signal strength is unknown.
+        int asu = mSignalStrength;
+        if (asu <= 2 || asu == 99) {
+            level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_GREAT) {
+            level = SIGNAL_STRENGTH_GREAT;
+        } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_GOOD) {
+            level = SIGNAL_STRENGTH_GOOD;
+        } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_MODERATE) {
+            level = SIGNAL_STRENGTH_MODERATE;
+        } else {
+            level = SIGNAL_STRENGTH_POOR;
+        }
+        if (DBG) log("getLevel=" + level);
+        return level;
+    }
+
+    /**
+     * Get the signal strength as dBm
+     */
+    @Override
+    public int getDbm() {
+        int dBm;
+
+        int level = mSignalStrength;
+        int asu = (level == 99 ? Integer.MAX_VALUE : level);
+        if (asu != Integer.MAX_VALUE) {
+            dBm = -113 + (2 * asu);
+        } else {
+            dBm = Integer.MAX_VALUE;
+        }
+        if (DBG) log("getDbm=" + dBm);
+        return dBm;
+    }
+
+    /**
+     * Get the signal level as an asu value between 0..31, 99 is unknown
+     * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
+     */
+    @Override
+    public int getAsuLevel() {
+        // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+        // asu = 0 (-113dB or less) is very weak
+        // signal, its better to show 0 bars to the user in such cases.
+        // asu = 99 is a special case, where the signal strength is unknown.
+        int level = mSignalStrength;
+        if (DBG) log("getAsuLevel=" + level);
+        return level;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSignalStrength, mBitErrorRate);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        CellSignalStrengthTdscdma s;
+
+        try {
+            s = (CellSignalStrengthTdscdma) o;
+        } catch (ClassCastException ex) {
+            return false;
+        }
+
+        if (o == null) {
+            return false;
+        }
+
+        return mSignalStrength == s.mSignalStrength
+                && mBitErrorRate == s.mBitErrorRate
+                && mRscp == s.mRscp;
+    }
+
+    /**
+     * @return string representation.
+     */
+    @Override
+    public String toString() {
+        return "CellSignalStrengthTdscdma:"
+                + " ss=" + mSignalStrength
+                + " ber=" + mBitErrorRate
+                + " rscp=" + mRscp;
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (DBG) log("writeToParcel(Parcel, int): " + toString());
+        dest.writeInt(mSignalStrength);
+        dest.writeInt(mBitErrorRate);
+        dest.writeInt(mRscp);
+    }
+
+    /**
+     * Construct a SignalStrength object from the given parcel
+     * where the token is already been processed.
+     */
+    private CellSignalStrengthTdscdma(Parcel in) {
+        mSignalStrength = in.readInt();
+        mBitErrorRate = in.readInt();
+        mRscp = in.readInt();
+        if (DBG) log("CellSignalStrengthTdscdma(Parcel): " + toString());
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    @SuppressWarnings("hiding")
+    public static final Parcelable.Creator<CellSignalStrengthTdscdma> CREATOR =
+            new Parcelable.Creator<CellSignalStrengthTdscdma>() {
+        @Override
+        public CellSignalStrengthTdscdma createFromParcel(Parcel in) {
+            return new CellSignalStrengthTdscdma(in);
+        }
+
+        @Override
+        public CellSignalStrengthTdscdma[] newArray(int size) {
+            return new CellSignalStrengthTdscdma[size];
+        }
+    };
+
+    /**
+     * log
+     */
+    private static void log(String s) {
+        Rlog.w(LOG_TAG, s);
+    }
+}
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index dd32a96..21cf0be 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -35,7 +35,13 @@
     private static final int WCDMA_SIGNAL_STRENGTH_MODERATE = 5;
 
     private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
-    private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
+                                 // or Integer.MAX_VALUE if unknown
+    private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+                               // Integer.MAX_VALUE if unknown
+    private int mRscp; // bit error rate (0-96, 255) as defined in TS 27.007 8.69 or
+                       // Integer.MAX_VALUE if unknown
+    private int mEcNo; // signal to noise radio (0-49, 255) as defined in TS 27.007 8.69 or
+                       // Integer.MAX_VALUE if unknown
 
     /** @hide */
     public CellSignalStrengthWcdma() {
@@ -43,9 +49,11 @@
     }
 
     /** @hide */
-    public CellSignalStrengthWcdma(int ss, int ber) {
+    public CellSignalStrengthWcdma(int ss, int ber, int rscp, int ecno) {
         mSignalStrength = ss;
         mBitErrorRate = ber;
+        mRscp = rscp;
+        mEcNo = ecno;
     }
 
     /** @hide */
@@ -57,6 +65,8 @@
     protected void copyFrom(CellSignalStrengthWcdma s) {
         mSignalStrength = s.mSignalStrength;
         mBitErrorRate = s.mBitErrorRate;
+        mRscp = s.mRscp;
+        mEcNo = s.mEcNo;
     }
 
     /** @hide */
@@ -70,10 +80,15 @@
     public void setDefaultValues() {
         mSignalStrength = Integer.MAX_VALUE;
         mBitErrorRate = Integer.MAX_VALUE;
+        mRscp = Integer.MAX_VALUE;
+        mEcNo = Integer.MAX_VALUE;
     }
 
     /**
-     * Get signal level as an int from 0..4
+     * Retrieve an abstract level value for the overall signal strength.
+     *
+     * @return a single integer from 0 to 4 representing the general signal quality.
+     *     0 represents very poor signal strength while 4 represents a very strong signal strength.
      */
     @Override
     public int getLevel() {
@@ -145,7 +160,10 @@
             return false;
         }
 
-        return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate;
+        return mSignalStrength == s.mSignalStrength
+                && mBitErrorRate == s.mBitErrorRate
+                && mRscp == s.mRscp
+                && mEcNo == s.mEcNo;
     }
 
     /**
@@ -155,7 +173,9 @@
     public String toString() {
         return "CellSignalStrengthWcdma:"
                 + " ss=" + mSignalStrength
-                + " ber=" + mBitErrorRate;
+                + " ber=" + mBitErrorRate
+                + " rscp=" + mRscp
+                + " ecno=" + mEcNo;
     }
 
     /** Implement the Parcelable interface */
@@ -164,6 +184,8 @@
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
         dest.writeInt(mSignalStrength);
         dest.writeInt(mBitErrorRate);
+        dest.writeInt(mRscp);
+        dest.writeInt(mEcNo);
     }
 
     /**
@@ -173,6 +195,8 @@
     private CellSignalStrengthWcdma(Parcel in) {
         mSignalStrength = in.readInt();
         mBitErrorRate = in.readInt();
+        mRscp = in.readInt();
+        mEcNo = in.readInt();
         if (DBG) log("CellSignalStrengthWcdma(Parcel): " + toString());
     }
 
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index bba779d..e881549 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -85,12 +85,12 @@
     public static final int SERVICE_TYPE_VIDEO = 4;
     public static final int SERVICE_TYPE_EMERGENCY = 5;
 
-    /** {@link AccessNetworkConstants.TransportType}*/
-    private final int mTransportType;
-
     @Domain
     private final int mDomain;
 
+    /** {@link AccessNetworkConstants.TransportType}*/
+    private final int mTransportType;
+
     @RegState
     private final int mRegState;
 
@@ -112,19 +112,19 @@
     private DataSpecificRegistrationStates mDataSpecificStates;
 
     /**
-     * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
      * @param domain Network domain. Must be DOMAIN_CS or DOMAIN_PS.
+     * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
      * @param regState Network registration state.
      * @param accessNetworkTechnology See TelephonyManager NETWORK_TYPE_XXXX.
      * @param reasonForDenial Reason for denial if the registration state is DENIED.
      * @param availableServices The supported service.
      * @param cellIdentity The identity representing a unique cell
      */
-    public NetworkRegistrationState(int transportType, int domain, int regState,
+    public NetworkRegistrationState(int domain, int transportType, int regState,
             int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
             int[] availableServices, @Nullable CellIdentity cellIdentity) {
-        mTransportType = transportType;
         mDomain = domain;
+        mTransportType = transportType;
         mRegState = regState;
         mAccessNetworkTechnology = accessNetworkTechnology;
         mReasonForDenial = reasonForDenial;
@@ -137,11 +137,11 @@
      * Constructor for voice network registration states.
      * @hide
      */
-    public NetworkRegistrationState(int transportType, int domain, int regState,
+    public NetworkRegistrationState(int domain, int transportType, int regState,
             int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
             int[] availableServices, @Nullable CellIdentity cellIdentity, boolean cssSupported,
             int roamingIndicator, int systemIsInPrl, int defaultRoamingIndicator) {
-        this(transportType, domain, regState, accessNetworkTechnology,
+        this(domain, transportType, regState, accessNetworkTechnology,
                 reasonForDenial, emergencyOnly, availableServices, cellIdentity);
 
         mVoiceSpecificStates = new VoiceSpecificRegistrationStates(cssSupported, roamingIndicator,
@@ -152,18 +152,18 @@
      * Constructor for data network registration states.
      * @hide
      */
-    public NetworkRegistrationState(int transportType, int domain, int regState,
+    public NetworkRegistrationState(int domain, int transportType, int regState,
             int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
             int[] availableServices, @Nullable CellIdentity cellIdentity, int maxDataCalls) {
-        this(transportType, domain, regState, accessNetworkTechnology,
+        this(domain, transportType, regState, accessNetworkTechnology,
                 reasonForDenial, emergencyOnly, availableServices, cellIdentity);
 
         mDataSpecificStates = new DataSpecificRegistrationStates(maxDataCalls);
     }
 
     protected NetworkRegistrationState(Parcel source) {
-        mTransportType = source.readInt();
         mDomain = source.readInt();
+        mTransportType = source.readInt();
         mRegState = source.readInt();
         mAccessNetworkTechnology = source.readInt();
         mReasonForDenial = source.readInt();
@@ -260,8 +260,8 @@
     @Override
     public String toString() {
         return new StringBuilder("NetworkRegistrationState{")
-                .append("transportType=").append(mTransportType)
                 .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS")
+                .append("transportType=").append(mTransportType)
                 .append(" regState=").append(regStateToString(mRegState))
                 .append(" accessNetworkTechnology=")
                 .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
@@ -290,8 +290,8 @@
         }
 
         NetworkRegistrationState other = (NetworkRegistrationState) o;
-        return mTransportType == other.mTransportType
-                && mDomain == other.mDomain
+        return mDomain == other.mDomain
+                && mTransportType == other.mTransportType
                 && mRegState == other.mRegState
                 && mAccessNetworkTechnology == other.mAccessNetworkTechnology
                 && mReasonForDenial == other.mReasonForDenial
@@ -305,8 +305,8 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mTransportType);
         dest.writeInt(mDomain);
+        dest.writeInt(mTransportType);
         dest.writeInt(mRegState);
         dest.writeInt(mAccessNetworkTechnology);
         dest.writeInt(mReasonForDenial);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index ae999c3..9e8529e 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1569,13 +1569,14 @@
     /**
      * Get the network registration states with given transport type and domain.
      *
+     * @param domain The network domain. Must be {@link NetworkRegistrationState#DOMAIN_CS} or
+     * {@link NetworkRegistrationState#DOMAIN_PS}.
      * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType}
-     * @param domain The network domain. Must be DOMAIN_CS or DOMAIN_PS.
      * @return The matching NetworkRegistrationState.
      * @hide
      */
     @SystemApi
-    public NetworkRegistrationState getNetworkRegistrationStates(int transportType, int domain) {
+    public NetworkRegistrationState getNetworkRegistrationStates(int domain, int transportType) {
         synchronized (mNetworkRegistrationStates) {
             for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) {
                 if (networkRegistrationState.getTransportType() == transportType
diff --git a/test-base/Android.bp b/test-base/Android.bp
index a0e3985..d25b477 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -21,6 +21,7 @@
 // Also contains the com.android.internal.util.Predicate[s] classes.
 java_library {
     name: "android.test.base",
+    installable: true,
 
     srcs: ["src/**/*.java"],
 
@@ -42,6 +43,7 @@
 // Also contains the com.android.internal.util.Predicate[s] classes.
 java_library {
     name: "legacy-test",
+    installable: true,
 
     sdk_version: "current",
     static_libs: ["android.test.base"],
@@ -115,4 +117,5 @@
         },
     },
     sdk_version: "current",
+    compile_dex: true,
 }
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 51fa86b..8d3faae 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -18,6 +18,7 @@
 // ===================================
 java_library {
     name: "android.test.mock",
+    installable: true,
 
     java_version: "1.8",
     srcs: ["src/**/*.java"],
@@ -91,6 +92,7 @@
             enabled: false,
         },
     },
+    compile_dex: true,
 }
 
 java_library_static {
@@ -104,4 +106,5 @@
             enabled: false,
         },
     },
+    compile_dex: true,
 }
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index b50ba3b..2caa6c4 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -18,6 +18,7 @@
 // =====================================
 java_library {
     name: "android.test.runner",
+    installable: true,
 
     // Needs to be consistent with the repackaged version of this make target.
     java_version: "1.8",
@@ -120,4 +121,5 @@
         },
     },
     sdk_version: "current",
+    compile_dex: true,
 }
diff --git a/vr/Android.bp b/vr/Android.bp
index b5904d6..775ec96 100644
--- a/vr/Android.bp
+++ b/vr/Android.bp
@@ -26,6 +26,7 @@
 // Java platform library for vr stuff.
 java_library {
     name: "com.google.vr.platform",
+    installable: true,
     owner: "google",
     required: [
         "libdvr_loader",